erosman / support

Support Location for all my extensions
Mozilla Public License 2.0
175 stars 12 forks source link

[Firemonkey] Script working with Tampermonkey but failing with Firemonkey #343

Closed EstherMoellman closed 3 years ago

EstherMoellman commented 3 years ago

Hi my friend @erosman ! I hope you and your family are doing well.

If you remember, time ago you kindly helped me to adapt this script by replacing line 16 (fn = (a) => {) with:

fn = (a) => { let fn, ipse, haia, hca, rpo, et;

... and was wonderful, it worked perfectly for months and months! (thank you).

But days ago, suddenly, an undesirable behavior appeared: The script still works, Youtube ads are blocked. But sometimes the ad time interval is not blocked. Before video starting, a black background appears for 5 to 10 seconds (no ad images)... only then the video starts.

Of course YT page periodically has its new changes. But the author of this script is a great guy, and he keeps the script updated/upgraded most of the time. Today I contacted him, I reported him my issue, and he said "no issues" from his side (using Tampermonkey). And indeed, I tested the script with Tampermonkey, and it works perfectly.

Last Firemonkey update was 10 days ago. Do you believe this update can explain the issue with this script? Please, any recommendation for me and this script?

Zillions of thanks as usual, and big hug! : )

erosman commented 3 years ago

Hi Esther

Does the script work with GreaseMonkey (or ViolentMonkey)?

Just for Info .... The workaround author has put in may no longer be necessary with new @inject-to (in script or as user setting). Which "partial compatibility to GreaseMonkey specification" does the author refer to regarding the script?

  if (this.GM_info && (this.GM_info.scriptHandler === "FireMonkey")) {
    //workaround for FireMonkey's partial compatibility to GreaseMonkey specification.
    let e = document.createElement("SCRIPT");
    e.id = "dyvaUjs";
    e.text = "(" + fn + ")()";
    document.documentElement.appendChild(e);
  } else fn();
EstherMoellman commented 3 years ago

@erosman thanks for your replay & help!

GreaseMonkey: The script is not working (YT page broken - partially loaded). ViolentMonkey: The script is not working (YT page fully loaded, but ads are not blocked). TamperMonkey: The script works (but in settings, "Inject Mode" must be changed to "Instant"). FireMonkey: The script partially works (YT page fully loaded, ads are blocked, but black background appears for 5 to 10 seconds before video starting). It also needs a small hack at script line 16.

With regards to your @inject-to explanation, if I well understood you, the part of the code you posted should be deleted, but please (sorry for my ignorant question), do I need to put @inject-to at the beginning of the script? Any other command after @inject-to?

The author of the YT script is a very nice guy, very productive, he has lot of js scripts, and he uses to keep everything updated/upgraded. However, he is a TamperMonkey user, and many times he clearly stated (in a very polite way) that "his js codes are meant to work with TamperMonkey, and he has not time to adapt his js codes to other monkeys". Long time ago I met him at Reddit, and introduced FireMonkey. He liked FM! (at the point he opened an exception, and he made a FM adaptation to his YT js script). You also helped me in the past with this YT script, and it worked perfectly with FM for a long time (so the author doesn't know about the huge FM updates/upgrades you made during the last months). Today I sent him a message explaining the great FM changes (and also asked him the questions you posted in your message - GreaseMonkey specification + page context). He answered me saying that he'll take a look and will check the latest FM, but he also said to me (very politely) that he is too busy and won't be able to help with possible future FM adaptations to his js codes.

I would like to have this YT js code working with FM... but is not my main focus here. Sometimes I see js code incompatibilities or the need for FM code adaptations as an opportunity to introduce FM to js code developers. Specially in the case of this YT js code author, a very productive js developer, I see any FM incompatibility/adaptation as an opportunity to incentive him to test/try FM. I know you're very busy, but if by chance you can give me a hand, I'll exploit the FM incompatibility with this YT js code as an opportunity/excuse to keep author attention/interest on FM, and to produce js codes adapted to FM.

Thks again!

erosman commented 3 years ago

The reason I asked was to determine where the issue could be.

GreaseMonkey: The script is not working (YT page broken - partially loaded). ViolentMonkey: The script is not working (YT page fully loaded, but ads are not blocked).

Since it is not working in GM & TM, then the issue is not with FM standard comparability. The issue should also be unrelated to content (GM|TM|VM) vs userScript context (FM). Since TM only inject into page context when @grant none, then page context should also not be the issue.

Note: The workaround that the script developer added for FM, injects the script into page context. The "partial compatibility to GreaseMonkey specification" assertion seems out of place since the script doesn't work with GM (or VM) either.

TamperMonkey: The script works (but in settings, "Inject Mode" must be changed to "Instant").

Inject Mode is under Experimental in TM Settings. I didn't find the documentation for it but it seems TM would inject the script faster which should not be the issue with FM since it has true @run-at document-start.

Let me see what I can find out.

erosman commented 3 years ago

Can you set the @inject-into after this line and see what happens?

...
...
// @grant       unsafeWindow
// @run-at      document-start
// @inject-into        page
// ==/UserScript==
EstherMoellman commented 3 years ago

@erosman I made lot of new tests and have some fresh info:

1) The part of the code you mentioned in your first message - lines 323 to 329: "if (this.GM_info && (this.GM_info.scriptHandler === "FireMonkey")) {... } else fn();" indeed still has functional effect in FM. If I remove this part of the code, most of the ads are not blocked at all. And vice-versa, keeping this part of the code... most of the ads are totally blocked.

2) I tested several different combinations of @inject-into: // @inject-into ://.youtube.com/ // @inject-into https://www.youtube.com/ // @inject-into https://www.youtube.com // @inject-into www.youtube.com Etc

No one seems to have effect. That said, by testing: // @inject-into www.youtube.com I saw less ads. I can't confirm, it can be a placebo effect, or a random effect. My wild guess is that @inject-into has no effect with this YT code.

3) After messing with this YT script, adding and removing parts, without logical explanation the black background (before video starting) disappeared (the same happened with the 5 to 10 seconds time delay). This drove me to think that my FM might be "dirty" (corrupted). So I decided to make a clean test (clean Firefox and clean FireMonkey install). And voilà, Black background and time delay are gone! However, 50% of the time an ad image appears, just the ad image, and when you press "play", no ads (are blocked), video starts directly.

In short, my impressions:

Thanks for your time and patience! : )

erosman commented 3 years ago

The part of the code you mentioned in your first message - lines 323 to 329:

That part injects the code into into page context. If that works, then that would mean code is meant to run in page context. However, TM only injects into page context when @grant none. :man_shrugging:

📌 Can you ask the script developer if the code is meant to run in page context?

I tested several different combinations of @inject-into:

@inject-into refers to context, not URL, and the only valid value currently in FM, is page. The function of @inject-into is what the script developer did in lines 323 to 329.

... clean FM install, ....

That should not matter. The different result is probably due to browser caching and nothing to do with FM.

EstherMoellman commented 3 years ago

📌 Can you ask the script developer if the code is meant to run in page context?

As I mentioned in my second message (above), I already asked him (6 hours ago), I'm waiting his answer.

@inject-into refers to context, not URL, and the only valid value currently in FM, is page.

In my previous message, point "2", the word "Etc" included my tests with "// @inject-into page" (sorry if I wasn't explicit enough). In brief, I tested lot of variants (including // @inject-into page) and nothing worked in my tests.

... clean FM install, ....

That should not matter. The different result is probably due to browser caching and nothing to do with FM.

You're the expert and surely you're right. But it was not my first time having problems with "corrupted" FireMonkeys (after long time using them, or after updates/upgrades using Nightly). Also, I never use caching (I disabled all kind of caching). For me the simplest solution for this kind of FM corruption always was to remove and reinstall a brand new clean FireMonkey. But I shouldn't call this "FM corruption" : ) because I believe this is a Nightly problem. Also, I'm too curious and all the time I mess with my Nightly, so this may contribute to corrupt my files, add-ons etc. My FM was not corrupted, but I believe my Nightly corrupted storage files.

I will remain waiting for script author answer (page context). But please, feel free to close this issue if you believe nothing else can be done to fix this script.

Again, again and again... thanks! : )

EstherMoellman commented 3 years ago

Hi @erosman , here are the answers to your questions (I quote the author):

1) With regards to code line 323: Which "partial compatibility to GreaseMonkey specification" does the author refer to regarding the script?

ANSWER: The FireMonkey compatibility code is needed since scripts run by FireMonkey are run not in page context, by default.

2) Is the script meant to run in page context?

ANSWER: The script is meant to be run in page context. In fact, GM scripts are supposed to run in page context - whether it's in a sandbox or not. This part is where FireMonkey is not compatible with GreaseMonkey's specification.

@erosman , please remember that the author originally included FireMonkey adaptations and comments to his script long time ago, before current FireMonkey improvements/updates/upgrades. Therefore, the author at that time was referring to an old version of FireMonkey. As I explained, the author is a TamperMonkey user, not interested on other monkeys or script adaptations for any other monkey. So, the author doesn't know about the latest FireMonkey. That said, I would like to repeat that based on my tests, the part of the script (lines 323 to 329) still has a functional effect on FireMonkey (if these lines are removed, ads are not blocked). The // @inject-into page had no effects on my tests.

Changing subject, in order to make the script compatible with FireMonkey, the author kindly incorporated your hack at line 16: fn = (a) => {) with: fn = (a) => { let fn, ipse, haia, hca, rpo, et; An updated script can be found here. Just remembering, the issue is still there: YT page loads perfectly, ads are perfectly blocked, but an ad image appears before video starting (when "play" is pressed, no ads, video starts directly). This was not happening days ago, for long time (more than one year) the script perfectly worked along FireMonkey (no ad images before videos). At present moment the script works perfectly with TamperMonkey (no ad images before videos).

@erosman , the script itself is kind of irrelevant, and if you are busy or can't help, please feel free to close the issue. But if the issue with this script helps you to find something to improve at FireMonkey, then please keep counting with my collaboration.

Have a nice Sunday! : )

EDITED

erosman commented 3 years ago

With regards to code line 323: Which "partial compatibility to GreaseMonkey specification" does the author refer to regarding the script?

ANSWER: The FireMonkey compatibility code is needed since scripts run by FireMonkey are run not in page context, by default.

The script is meant to be run in page context. In fact, GM scripts are supposed to run in page context - whether it's in a sandbox or not. This part is where FireMonkey is not compatible with GreaseMonkey's specification.

The script developers is unaware of contexts.

Please read FireMonkey Help under inject-into for more information.

Changing subject, in order to make the script compatible with FireMonkey, the author kindly incorporated your hack at line 16: fn = (a) => {) with: fn = (a) => { let fn, ipse, haia, hca, rpo, et;

That is not a hack. It is basic JavaScript that runs in strict mode (where "bad syntax" cause error). GM|TM|VM dont run in strict mode, therefore "bad syntax" does cause error but doesn't halt the script. In strict mode, "bad syntax" halts the running of the script. JS developers should aim to avoid "bad syntax".

For example, look at these randomly selected userscripts that have 'use strict' and aim to avoid "bad syntax" Local YouTube Downloader Remove web limits(modified) Greasyfork Search with Sleazyfork Results include Userscript+ : Show Site All UserJS ..... etc

Look for something like this ....

// ==/UserScript==
(function() {
    'use strict';   <------------

Please check the error/warning code from JSHint in the FM CodeMirror display as commenting on issues might cause offence.

@EstherMoellman If you can find someone to fix the errors in the code, and update the code into modern JavaScript standard, I will be able to check and see how it can be run as you expect, in FireMonkey. I started with it but there where too many and too time consuming.

EstherMoellman commented 3 years ago

@erosman ,

Thanks a lot for your explanations, teachings and time! (I'm sure you're very busy dealing with more important stuff than this YT js script). Really was/is most appreciated the attention you dedicated me! Thanks! :)

Today in my previous message I quoted author answers without understanding big parts of these answers. I also don't have knowledge to fully understand your explanations/teachings (your last message). But I'll share your arguments with the script developer. That said, considering that:

... honestly @erosman , I believe that the developer (unfortunately) is not going to be interested in this discussion.

Regardless author comments and your arguments, I think it might be interesting for you that:

The YT script is working perfectly on TamperMonkey, so I don't believe that issues with this script are related to changes in the YT page. And even the script having "bad syntax", it works with TamperMonkey, so is interesting to know how TamperMonkey deals with bad syntax. Therefore, I discard the YT page and the YT script as the source of my issues.

I thought that the problem could be Firefox (my Nightly). So today I made a test on Beta and Normal Vanilla release. But nope, the issues remain there, so I discard Firefox as the source of the issues.

I'm not going to say that FireMonkey is the source of the issues. But I discarded everything, and I don't have another variable to look after.

Always important to remember that:

@EstherMoellman If you can find someone to fix the errors in the code, and update the code into modern JavaScript standard, I will be able to check and see how it can be run as you expect, in FireMonkey. I started with it but there where too many and too time consuming.

Thanks @erosman . Before contacting you, I already tried this option. I tested all YT ad blocking scripts, and the only script that worked (for a long time) like a charm, was the YT script I presented here in your Github issue. That's the reason why I'm so interested in this script, again, is the only script working with FireMonkey (for YT ad blocking). Before contacting you I shared the script with other developers. They tried to help, but I don't think they have the level of knowledge that author script has. Also, most of the devs unfortunately don't work with FireMonkey... so it's not easy for us (users) to solve issues (script devs blame extensions, extension devs blame scripts, sometimes the problem is the browser, sometimes is the webpage itself etc).

In the other hand, I believe I already took from you enough time. And certainly you have more important things to do. And as usual, you have been more than kind, useful and helpful. So if you agree... I suggest to close this issue as "unsolved".

As I said, please just keep in your mind not this YT script itself, but things that indirectly emerge from the issues with this YT script, and that may help you to identify & solve possible problems with FireMonkey, or might help you to improve FireMonkey.

Thanks again and big hug always! : )

erosman commented 3 years ago

Author comments and FireMonkey adaptations were related to very old FireMonkey versions

Even for the old FireMonkey, the concept of context remains the same.

FYI .... context are layers of privilege, created to ensure security in browsers which are the following in order of privilege:

  1. Top Privilege Context: browser context
  2. Lower than above Privilege Context: content/contentScript context
  3. Lower than above Privilege Context: userScript context
  4. Lowest of all Privilege Context: page context

... I believe that the developer (unfortunately) is not going to be interested in this discussion.

Understandable since the developers hasn't made the script compatible with GM or VM either.

Script lines 323 to 329 still have functional effect on FireMonkey (if these lines are removed, ads are not blocked). The "// @inject-into page" had no effects on my tests.

That was something for the future. I don't think the problem is this.

The YT script is working perfectly on TamperMonkey, so I don't believe that issues with this script are related to changes in the YT page.

There are great many scripts that only work with TM. The reason is that TM handles userscripts differently and since its code is not open-source, everything is not apparent. One of the major differences in TM is the fact that it alters page CSP. This behaviour is exclusive to TM and not compatible with GM|VM|FM. Please read the FM Help for more info.

And even the script having "bad syntax", it works with TamperMonkey, so is interesting to know how TamperMonkey deals with bad syntax.

Shouldn't script developers aspire towards good syntax instead of expecting extensions to disregard bad syntax?! ;) userScript context runs in strict mode. It is not set by FM. It is how Mozilla has designed the userScript API since strict mode is the modern standard.

I thought that the problem could be Firefox (my Nightly).

Although Nightly has its quirks, I don't think it is the issue here.

Always important to remember that: .....

I understand that due to changes to YT, scripts has changed and somehow these changes are not applied when script runs in FM. It could be one of those "bad syntax" instances or something else. The script developer is the best person to check it since the developer is usually intimately familiar with what is happening in the script.

Before contacting you I shared the script with other developers. They tried to help, but I don't think they have the level of knowledge that author script has. Also, most of the devs unfortunately don't work with FireMonkey...

I would have tried to help out by finding what is happening if the code was without errors and in modern JS standard. If you find someone to tidy-up the code, then I will check it out.


PS. Please note that as mentioned before, userscripts have to do a LOT of twist & turn and acrobatics to block ads. These actions affect the performance of the browser, especially on pages like YT or FB that are heavily JS generated. You might find that a proper ad-blocker not only blocks better but also improve the performance. I use uBlock Origin plus uMatrix.

Here is a screenshot from YT showing blocked ads by above.

youtube

EstherMoellman commented 3 years ago

Thank you again @erosman for all your comments.

Time ago I intensively used both UMatrix and UBlock. However, it was a time where browsers were "naked", almost no features against ads, tracking etc. Time passed, evolution took place, and browsers evolved a lot. Nowadays, by taking advantage of all browser built-in features, plus some few lightweight additional measures, is possible to have an efficient ad blocking functionality, without the need of add-ons. In addition to that, as you now, UMatrix is already archived (and there is a reason for that!). And in my opinion UBlock will follow similar way (or will remain alive, but just used by a very small user base). Even during their golden age, both add-ons never were massively adopted by users (add-on competitors until today have more users). Both extensions are great, but are dinosaurs. In brief, for me it's been two years without using ad blocker extensions. And never looked back!

By the way, you may not remember, but one of the reasons I fell in love with FireMonkey, was because FM helped me to remove several add-ons (including UMatrix). And considering that 90% of the js scripts I use are for specific single pages... I have no browser performance issues. I can't say the same about UMatrix/UBlock! (too heavy for my taste).

Nothing against users loving add-ons. It's just not my case. For ads I prefer browser built-in features, about:config prefs, chrome css, js scripts (FireMonkey) and another few OS level tools.

With regards to the YT script, as I said, I don't have another js dev. But again, I thank you for your offering. If you agree, I'm closing this issue, because at present moment seems unsolvable for me.

Thanks!

erosman commented 3 years ago

As an idea, you can try and post the same request to GM and VM support on Github, stating that the userscript doesn't work with GM or VM.

They both have thousands of users and many knowledgeable users who might find out the reason.

:wink:

EstherMoellman commented 3 years ago

@erosman , done, thanks for the tip. Now let's wait and see if anyone can help me there.

EstherMoellman commented 3 years ago

Hi @erosman,

The author of the YT ad blocker script made an update. I tested it and now works 100% with FireMonkey, GreaseMonkey and ViolentMonkey (and TamperMonkey).

The Dev was very kind, because despite repeating many times that he was only interested in TamperMonkey, he ended up doing the monkey adaptations, and the script is working again (on all monkeys). Good for him (nice attitude), good for the script and good for us users.

I'm sure you are busy with more important things to do. But I always love your opinion. And considering that the Dev said he cleaned, fixed and adapted everything he could in the latest update, please it would be great if you could take a look at the new script, and see if you would correct, add or remove anything. But if you can't or don't want to do that, please don't worry, I understand.

As always, thanks in advance! : )

https://greasyfork.org/en/scripts/32626-disable-youtube-video-ads

erosman commented 3 years ago

I am glad that the script is working for you.

The script is (has been) using plain JavaScript, with the exception of unsafeWindow mapped to window which in reality means the window in the userscript is the same window as the page (as would be the case for userscripts injected into page context).

At the moment ...

Considering all above, the userscript developer could have opted to inject into page context all the time and there would have been no need for @grant unsafeWindow and it would have worked the same way on all userscript managers.

JavaScript best practices

There are general rules/guidelines regarding good JavaScript e.g. JavaScript best practices, 50 JavaScript Best Practice Rules to Write Better Code etc

While above are guidelines, they improve performance and maintenance.

As an example, you asked me once about the var and the notice you get in FM CodeMirror editor.

Originally, JavaScript only had var to declare variables. Later the engineers found that since var is global, its performance is not as good as it could be. In order not to break current JS code, they left var as it was and instead created const & let in ES6 2015.

const & let are block scoped. They exist in their own block and are discarded after use while var has to be kept until page closes. Global var can also leak to areas/scopes that is not desired or expected. There is even a difference between const & let. const (constant) is for fixed values that don't change and let for values that can change.

Although on modern consumer computers, millions of JS functions can be performed in a single second, why would engineers bother to make 2 new JavaScript words and make a distinction between fixed and changeable ones? The answer is, because it matters.

Knowing that it matters, any JS developer that aspires for quality code would consider these facts.

There are many JavaScript best practices articles on the net, and although developers each have their own style of coding, after checking a few articles, a general consensus can be deduced.

erosman commented 3 years ago

Just for info ...

UserScript Injection into Page Context

Therefore, the following would simply take care of FM|TM|VM Note: It is also recommended to use the more robust @match instead of @include

// ==UserScript==
// @name          Disable YouTube Video Ads
// @namespace     DisableYouTubeVideoAds
// @version       1.2.27
// @license       AGPLv3
// @author        jcunews
// @website       https://greasyfork.org/en/users/85671-jcunews
// @description   Disable YouTube video & screen based ads at home page, and right before or in the middle of the main video playback. For new YouTube layout (Polymer) only.
// @match         https://www.youtube.com/*
// @grant         none
// @run-at        document-start
// @inject-into   page
// ==/UserScript==
EstherMoellman commented 3 years ago

FANTASTIC @erosman ! Thank you. I tested your suggested header and works perfectly. Any recommendation about "://.youtube.com/" ? Or is "https://www.youtube.com/" fine? Quoting you: "Although on modern consumer computers, millions of JS functions can be performed in a single second... because it matters"... so if tiny changes can give me milliseconds of performance improvements, I want that changes! That's the reason I ask you about using or not using RegExps in @match.

erosman commented 3 years ago
This will only apply to HTTPS page
// @match         https://www.youtube.com/*

This means HTTP, HTTPS, WS & WSS
// @match         *://www.youtube.com/*

In other words the same as:
// @match         http://www.youtube.com/*
// @match         https://www.youtube.com/*
// @match         ws://www.youtube.com/*
// @match         wss://www.youtube.com/*

Since YouTube is always using HTTPS it is good to use the first version i.e. https://www.youtube.com/*. The WS/WSS is not meant for userscripts.

FYI .... @match is not only more robust, but also in FM especially, it improves the performance. Read FM Help under URL Matching Performance.

erosman commented 3 years ago

Actually, the GM compatibility of the script is only for legacy GM3 since there is no GM_info in GM4 (someone might want to mention it to the dev).

I tested your suggested header and works perfectly.

Using that header means, part of the userscript code is not needed for FireMonkey and should be removed ... so don't use that heading (there is not a problem but it just does extra work for nothing).

Only change the @match part but don't add the // @inject-into page.

EstherMoellman commented 3 years ago

Using that header means, part of the userscript code is not needed for FireMonkey and should be removed

Please, just for my curiosity, which part? I tested keeping your recommended header + removing the FireMonkey adaptation (lines 365 to 371)... and the script is not working.

erosman commented 3 years ago

try ...

// ==UserScript==
// @name          Disable YouTube Video Ads
// @namespace     DisableYouTubeVideoAds
// @version       1.2.27
// @license       AGPLv3
// @author        jcunews
// @website       https://greasyfork.org/en/users/85671-jcunews
// @description   Disable YouTube video & screen based ads at home page, and right before or in the middle of the main video playback. For new YouTube layout (Polymer) only.
// @match         https://www.youtube.com/*
// @grant         none
// @run-at        document-start
// @inject-into   page
// ==/UserScript==

((window, fn) => {

  fn = (a, ipse, haia, hca, rpo, et) => {

    et = window.InstallTrigger ? "beforescriptexecute" : "message"; //Firefox workaround

    JSON.parse_dyva = JSON.parse;
    JSON.parse = function(a) {
      var m, z;
      if (rpo) {
        a = rpo; //JSON.parse_dyva(a); //from xhr/fetch
        try {
          if (a.forEach) {
            a.forEach((p, a) => {
              if (p.player && p.player.args && p.player.args.player_response) {
                a = p.player_response_; //JSON.parse_dyva(p.player_response);
                patchPlayerResponse(a); 
                p.player_response = JSON.stringify(a);
              } else if (p.playerResponse) {
                patchPlayerResponse(p.playerResponse);
              }
            });
          } else patchPlayerResponse(a);
        } catch(z) {}
        rpo = null;
      } else {
        a = JSON.parse_dyva(a);
        if (a.playerResponse) patchPlayerResponse(a.playerResponse);
      }
      return a;
    };

    var ftc = window.fetch_dyva = window.fetch;
    window.fetch = function(u) {
      if (u) {
        if (u.substr && /\/v1\/player\/ad_break/.test(u)) return new Promise(() => {});
        if (u.url && u.url.substr && /\/v1\/player\/ad_break/.test(u.url)) return new Promise(() => {});
      }
      return ftc.apply(this, arguments);
    };

    var rj = Response.prototype.json_dyva = Response.prototype.json;
    Response.prototype.json = function() {
      var rs = this, p = rj.apply(this, arguments), pt = p.then;
      p.then = function(fn) {
        var fn_ = fn;
        fn = function(j) {
          if (/\/v1\/player\?/.test(rs.url)) rpo = j;
          if ("function" === typeof fn_) return fn_.apply(this, arguments);
        };
        return pt.apply(this, arguments);
      };
      return p;
    };
    var rt = Response.prototype.text;
    Response.prototype.text = function() {
      var rs = this, p = rt.apply(this, arguments), pt = p.then;
      p.then = function(fn) {
        var fn_ = fn;
        fn = function(t) {
          if (/\/v1\/player\?/.test(rs.url)) rpo = JSON.parse_dyva(t);
          if ("function" === typeof fn_) return fn_.apply(this, arguments);
        };
        return pt.apply(this, arguments);
      };
      return p;
    };

    window.XMLHttpRequest.prototype.open_dyva = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function(mtd, url) {
      if (!(/get_midroll_info/).test(url) && !((/^\/watch/).test(location.pathname) && (/get_video_info/).test(url))) {
        this.url_dyva = url;
        return this.open_dyva.apply(this, arguments);
      }
    };
    window.XMLHttpRequest.prototype.addEventListener_dyva = window.XMLHttpRequest.prototype.addEventListener;
    window.XMLHttpRequest.prototype.addEventListener = function(typ, fn) {
      if (typ === "readystatechange") {
        var f = fn;
        fn = function() {
          var z;
          if ((this.readyState === 4) && (/\/watch\?|get_video_info/).test(this.url_dyva)) {
            rpo = JSON.parse_dyva(this.responseText);
            try {
              rpo.forEach(p => {
                if (p.player && p.player.args && p.player.args.player_response) {
                  p.playerResponse_ = JSON.parse_dyva(p.player_response);
                  if (p.playerResponse_.playabilityStatus && (p.playerResponse_.playabilityStatus.status === "LOGIN_REQUIRED")) {
                    nav.navigate({commandMetadata: {webCommandMetadata: {url: location.href, webPageType: "WEB_PAGE_TYPE_BROWSE"}}}, false);
                    return;
                  }
                  patchPlayerResponse(p.playerResponse_);
                  p.player_response = JSON.stringify(p.playerResponse_);
                } else if (p.playerResponse) {
                  patchPlayerResponse(p.playerResponse);
                }
              });
            } catch(z) {}
          }
          return f.apply(this, arguments);
        };
      }
      return this.addEventListener_dyva.apply(this, arguments);
    };

    var img = window.Image, imgsrc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, "src");
    window.Imageq = function() {
      var r = new img();
      Object.defineProperty(r, "src", {
        get: function() {
          return imgsrc.get.apply(this, arguments);
        },
        set: function(v) {
          if (/\/pagead\/|\/ads\?/.test(v)) return v;
          return imgsrc.set.apply(this, arguments);
        }
      });
      return r;
    };

    window.Node.prototype.appendChild_dyva = window.Node.prototype.appendChild;
    window.Node.prototype.appendChild = function(node) {
      var a;
      if (!ipse && (a = document.querySelector('ytd-watch-flexy')) && (a = a.constructor.prototype) && a.isPlaShelfEnabled_) {
        a.isPlaShelfEnabled_ = () => false;
        ipse = true;
      }
      if ((!hca || !haia) && (a = document.querySelector('ytd-watch-next-secondary-results-renderer')) && (a = a.constructor.prototype)) {
        if (a.hasAllowedInstreamAd_ && !haia) {
          a.hasAllowedInstreamAd_ = () => false;
          haia = true;
        }
        if (a.hasCompanionAds_ && !hca) {
          a.hasCompanionAds_ = () => false;
          hca = true;
        }
      }
      if ((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && Array.from(node.childNodes).some((n, i) => {
        if (n.id === "masthead-ad") {
          n.remove();
          return true;
        }
      })); //window.Node.prototype.appendChild = window.Node.prototype.appendChild_dyva;
      if (node.querySelector && (a = node.querySelector('.ytp-ad-skip-button'))) a.click();
      return this.appendChild_dyva.apply(this, arguments);
    };

    function patchPlayerResponse(playerResponse) {
      if (playerResponse.adPlacements) playerResponse.adPlacements = [];
      if (playerResponse.playerAds) playerResponse.playerAds = [];
    }

    function patchPlayerArgs(args, a) {
      if (args.ad_device) args.ad_device = "0";
      if (args.ad_flags) args.ad_flags = 0;
      if (args.ad_logging_flag) args.ad_logging_flag = "0";
      if (args.ad_preroll) args.ad_preroll = "0";
      if (args.ad_slots) delete args.ad_slots;
      if (args.ad_tag) delete args.ad_tag;
      if (args.ad3_module) args.ad3_module = "0";
      if (args.adsense_video_doc_id) delete args.adsense_video_doc_id;
      if (args.afv) args.afv = false;
      if (args.afv_ad_tag) delete args.afv_ad_tag;
      if (args.allow_html5_ads) args.allow_html5_ads = 0;
      if (args.csi_page_type) args.csi_page_type = args.csi_page_type.replace(/watch7ad/, "watch7");
      if (args.enable_csi) args.enable_csi = "0";
      if (args.pyv_ad_channel) delete args.pyv_ad_channel;
      if (args.show_pyv_in_related) args.show_pyv_in_related = false;
      if (args.vmap) delete args.vmap;
      if (args.player_response) {
        a = JSON.parse_dyva(args.player_response);
        patchPlayerResponse(a);
        args.player_response = JSON.stringify(a);
      }
    }

    function patchSpf() {
      if (window.spf && !spf.request_dyva) {
        spf.request_dyva = spf.request;
        spf.request = function(a, b) {
          if (b && b.onDone) {
            var onDone_ = b.onDone;
            b.onDone = function(response) {
              var a = response;
              if (a && (/\/watch\?/).test(a.url) && (a = a.response) && (a = a.parts)) {
                a.forEach((p, a) => {
                  if (p.player && p.player.args && p.player.args.player_response) {
                    a = JSON.parse_dyva((p = p.player.args).player_response);
                    patchPlayerResponse(a);
                    p.player_response = JSON.stringify(a);
                  } else if (p.playerResponse) {
                    patchPlayerResponse(p.playerResponse);
                  }
                });
              }
              return onDone_.apply(this, arguments);
            };
          }
          return this.request_dyva.apply(this, arguments);
        };
        return;
      }
    }

    var ldh;

    function do1(ev, a) {

      if ((a = document.scripts[document.scripts.length - 1]) && /"adPlacements"/.test(a.text)) {
        a.text = a.text.replace(/"adPlacements"/, '"adPlacements":[],"zadPlacements"');
      }
      if (window.loadDataHook) {
        if (!window.loadDataHook.dyva) {
          ldh = window.loadDataHook;
          window.loadDataHook = function(ep, dt) {
            if (dt.playabilityStatus && (dt.playabilityStatus === "LOGIN_REQUIRED")) {
              location.href = location.href;
              throw "Ain't gonna login";
            }
            patchPlayerResponse(dt);
            return ldh.apply(this, arguments);
          };
          window.loadDataHook.dyva = true;
        }
      }
      if (window.ytcfg && window.ytcfg.set) {
        if (!window.ytcfg.set.dyva) {
          var ytcfgSet = window.ytcfg.set;
          window.ytcfg.set = function(ytConfig, ytValue){
            if (window.ytInitialPlayerResponse) {
              if (ytInitialPlayerResponse.playabilityStatus && (ytInitialPlayerResponse.playabilityStatus === "LOGIN_REQUIRED")) {
                location.href = location.href;
                throw "Ain't gonna login";
              }
              patchPlayerResponse(window.ytInitialPlayerResponse);
            }
            patchSpf();
            if (ytConfig) {
              var a;
              if (a = ytConfig.EXPERIMENT_FLAGS) {
                if (a.enable_auto_play_param_fix_for_masthead_ad) a.enable_auto_play_param_fix_for_masthead_ad = false;
                if (a.html5_check_both_ad_active_and_ad_info) a.html5_check_both_ad_active_and_ad_info = false;
                if (a.web_enable_ad_signals_in_it_context) a.web_enable_ad_signals_in_it_context = false;
              }
              if (ytConfig.SKIP_RELATED_ADS === false) ytConfig.SKIP_RELATED_ADS = true;
              if (ytConfig.TIMING_ACTION) ytConfig.TIMING_ACTION = ytConfig.TIMING_ACTION.replace(/watch7ad/, "watch7");
              if (a = ytConfig.TIMING_INFO) {
                if (a.yt_ad) a.yt_ad = 0;
                if (a.yt_ad_an) delete a.yt_ad_an;
                if (a.yt_ad_pr) a.yt_ad_pr = 0;
              }
              if (
                (a = ytConfig.WEB_PLAYER_CONTEXT_CONFIGS) && (a = a.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH) &&
                a.serializedExperimentFlags && a.serializedExperimentFlags.replace
              ) {
                a.serializedExperimentFlags = a.serializedExperimentFlags.replace(
                  /([a-z][^=]+)=([^&]+)/g, (s, a, b) => {
                    switch (a) {
                      case "enable_ad_break_end_time_on_pacf_tvhtml5": 
                      case "enable_auto_play_param_fix_for_masthead_ad":
                      case "html5_check_both_ad_active_and_ad_info": b = false; break;
                      case "web_enable_ad_signals_in_it_context":
                      case "web_player_gvi_wexit_adunit": b = false; break;
                    }
                    return a + "=" + b;
                  }
                );
              }
            }
            return ytcfgSet.apply(this, arguments);
          };
          window.ytcfg.set.dyva = true;
        }
      }
      if (window.yt) {
        if (window.yt.player && window.yt.player.Application) {
          if (window.yt.player.Application.create) {
            if (!window.yt.player.Application.create.dyva) {
              var ytPlayerApplicationCreate = window.yt.player.Application.create;
              window.yt.player.Application.create = function(id, ytPlayerConfig) {
                if ((id === "player-api") && ytPlayerConfig && ytPlayerConfig.args) {
                  if (ytPlayerConfig.args.raw_player_response) patchPlayerResponse(ytPlayerConfig.args.raw_player_response);
                  if (ytPlayerConfig.args.vmap) delete ytPlayerConfig.args.vmap;
                }
                return ytPlayerApplicationCreate.apply(this, arguments);
              };
              window.yt.player.Application.create.dyva = true;
            }
          }
          if (window.yt.player.Application.createAlternate) {
            if (!window.yt.player.Application.createAlternate.dyva) {
              var ytPlayerApplicationCreateAlternate = window.yt.player.Application.createAlternate;
              window.yt.player.Application.createAlternate = function(id, ytPlayerConfig) {
                if ((id === "player-api") && ytPlayerConfig && ytPlayerConfig.args) {
                  if (ytPlayerConfig.args.raw_player_response) patchPlayerResponse(ytPlayerConfig.args.raw_player_response);
                  if (ytPlayerConfig.args.vmap) delete ytPlayerConfig.args.vmap;
                }
                return ytPlayerApplicationCreateAlternate.apply(this, arguments);
              };
              window.yt.player.Application.createAlternate.dyva = true;
            }
          }
        }
        if (window.yt.setConfig) {
          if (!window.yt.setConfig.dyva) {
            var ytSetConfig = window.yt.setConfig;
            window.yt.setConfig = function(ytConfig){
              if (ytConfig && ytConfig.ADS_DATA) delete ytConfig.ADS_DATA;
              return ytSetConfig.apply(this, arguments);
            };
            window.yt.setConfig.dyva = true;
          }
        }
      }
      if (window.ytplayer && window.ytplayer.config && window.ytplayer.config.args) {
        if (!window.ytplayer.config.args.dvya) {
          patchPlayerArgs(window.ytplayer.config.args);
          window.ytplayer.config.args.dvya = true;
        }
      }
    }
    addEventListener(et, do1);
    if (et === "message") postMessage({});
    do1();

    addEventListener("spfpartprocess", function(ev) { //old youtube
      if (ev.detail && ev.detail.part && ev.detail.part.data &&
          ev.detail.part.data.swfcfg && ev.detail.part.data.swfcfg.args) {
        patchPlayerArgs(ev.detail.part.data.swfcfg.args);
      }
    }, true);

    addEventListener("load", a => {
      if (!(a = window.ayvp_cssOverride)) {
        a = document.createElement("STYLE");
        a.id = "ayvp_cssOverride";
        a.innerHTML = `\/*
.video-ads{display:none!important}
.ytp-ad-overlay-open .caption-window.ytp-caption-window-bottom{margin-bottom:4em}
.ytp-autohide .caption-window.ytp-caption-window-bottom, .ytp-hide-controls .caption-window.ytp-caption-window-bottom{margin-bottom:0!important}`;
        document.documentElement.appendChild(a);
      }
      if (et === "message") {
        if (document.readyState !== "complete") {
          postMessage({});
        } else removeEventListener(et, do1);
      }
    });
  };
 fn();

})(unsafeWindow);
EstherMoellman commented 3 years ago

Yeap (LOL)... your modified script works perfectly : ) Kudos to you @erosman !... @erosman wins again! (LOL)

Last question: Both scripts work perfectly. But comparing the two scripts, if I understood you, your modified version is "good-syntax", right? Changing the header and removing the redundant part of the code, makes this script better from a "good-syntax" point of view, right? But what about performance? Which script is better in terms of performance? Are both scripts the same? Or is your script better in terms of performance/efficiency?

erosman commented 3 years ago

But comparing the two scripts, if I understood you, your modified version is "good-syntax", right? Changing the header and removing the redundant part of the code, makes this script better from a "good-syntax" point of view, right?

No. The code has not changed (you can compare the 2 using a free compare program). The only difference is that it uses a feature that is built-in FM (and TM|VM) instead of manually injecting via those lines that were removed.

AFA "good-syntax", I have intentionally set a high standard for the JSHint linter in the FM editor. The notices that you get are a good indication, although they don't cover everything.

But what about performance? Which script is better in terms of performance? Are both scripts the same? Or is your script better in terms of performance/efficiency?

The original code

Using Built-in Feature

As you can see above, in the first method, 2 sets/copies of the code will exist and be evaluated/parsed, once in context and once in page, while in the second method, only one set.

EstherMoellman commented 3 years ago

@erosman, just FYI, your modified version works like a charm with FireMonkey and ViolentMonkey. It doesn't works with TamperMonkey (I used @include instead of @match). And at GreaseMonkey the YT page doesn't load at all (I used @include instead of @match).

Knowing that the Dev is a TamperMonkey user + he is not interested in other monkey adaptations, perhaps this explains why he uses the other header. I understand his side, he made the script for his own personal use, he has no time/interest to make adaptations to other monkeys (it works for him using TM, and that's enough for him), so not justifying, but perhaps this explains a bit why he uses the other script. Please ignore my comment if I'm wrong.

erosman commented 3 years ago

It doesn't works with TamperMonkey (I used @include instead of @match).

@include & @match is supported and recommended by all managers. That is not the issue.

I only looked at the it from the FM point of view of automatic injection into page context. It might require some tweaks on TM.

And at GreaseMonkey the YT page doesn't load at all (I used @include instead of @match).

As mentioned above .... re include/match GM requires manual injection (which was in the part of the code that got removed) as it doesn't have automatic injection into page context. The original script already has issues with GM since GM_Info is no longer supported by GM (since 2017).

I only gave you an example of the code since you asked. I didn't fix the code to run on all managers. That would be the up to the developer.

EstherMoellman commented 3 years ago

Yes, I did understand you made recommendations based only on FM and only from FM point of view... and I adopted them, I'll use your recommended version.

I just wanted to share with you the Dev point of view, not justifying nor defending him, but from his TM point of view + not interest in other monkeys, perhaps this is part of the explanation why he used the other header (which works with TM). Anyway, I sent to the Dev all your great explanations (including the GM issues). If he wants, he can use your teachings to improve his script.

PS: I used @include because in some monkeys @match wasn't working. I can't explain why.

erosman commented 3 years ago

I used @include because in some monkeys @match wasn't working. I can't explain why.

That should not be the case as @march has been supported by all of them for a LONG time. The problem must be somewhere else.

Greasemonkey The @match metadata imperative is very similar to @include, however it is safer. It sets more strict rules on what the * character means.

Violentmonkey Define rules to decide whether a script should be executed. It is recommended to use @match instead of @include.

Tampermonkey More or less equal to the @include tag.

erosman commented 3 years ago

It doesn't works with TamperMonkey

I know what the problem is. usafeWindow is undefined in TM in page context.

Try this for FM|GM|VM|TM ... only changed the compatibility issue. The rest of the code is as it was. Note: Optional chaining (?.) is supprted by Firefox 74+, Chrome 80+ It is possible to write the code line 366 without Optional chaining for older browsers but it will be a bit longer.

// ==UserScript==
// @name          Disable YouTube Video Ads
// @namespace     DisableYouTubeVideoAds
// @version       1.2.27a
// @license       AGPLv3
// @author        jcunews
// @website       https://greasyfork.org/en/users/85671-jcunews
// @description   Disable YouTube video & screen based ads at home page, and right before or in the middle of the main video playback. For new YouTube layout (Polymer) only.
// @match         https://www.youtube.com/*
// @grant         none
// @run-at        document-start
// @inject-into   page
// ==/UserScript==

(() => {

  let fn = (a, ipse, haia, hca, rpo, et) => {

    if ((a = document.scripts[document.scripts.length - 1]) && (a.id === "dyvaUjs")) a.remove();

    et = window.InstallTrigger ? "beforescriptexecute" : "message"; //Firefox workaround

    JSON.parse_dyva = JSON.parse;
    JSON.parse = function(a) {
      var m, z;
      if (rpo) {
        a = rpo; //JSON.parse_dyva(a); //from xhr/fetch
        try {
          if (a.forEach) {
            a.forEach((p, a) => {
              if (p.player && p.player.args && p.player.args.player_response) {
                a = p.player_response_; //JSON.parse_dyva(p.player_response);
                patchPlayerResponse(a); 
                p.player_response = JSON.stringify(a);
              } else if (p.playerResponse) {
                patchPlayerResponse(p.playerResponse);
              }
            });
          } else patchPlayerResponse(a);
        } catch(z) {}
        rpo = null;
      } else {
        a = JSON.parse_dyva(a);
        if (a.playerResponse) patchPlayerResponse(a.playerResponse);
      }
      return a;
    };

    var ftc = window.fetch_dyva = window.fetch;
    window.fetch = function(u) {
      if (u) {
        if (u.substr && /\/v1\/player\/ad_break/.test(u)) return new Promise(() => {});
        if (u.url && u.url.substr && /\/v1\/player\/ad_break/.test(u.url)) return new Promise(() => {});
      }
      return ftc.apply(this, arguments);
    };

    var rj = Response.prototype.json_dyva = Response.prototype.json;
    Response.prototype.json = function() {
      var rs = this, p = rj.apply(this, arguments), pt = p.then;
      p.then = function(fn) {
        var fn_ = fn;
        fn = function(j) {
          if (/\/v1\/player\?/.test(rs.url)) rpo = j;
          if ("function" === typeof fn_) return fn_.apply(this, arguments);
        };
        return pt.apply(this, arguments);
      };
      return p;
    };
    var rt = Response.prototype.text;
    Response.prototype.text = function() {
      var rs = this, p = rt.apply(this, arguments), pt = p.then;
      p.then = function(fn) {
        var fn_ = fn;
        fn = function(t) {
          if (/\/v1\/player\?/.test(rs.url)) rpo = JSON.parse_dyva(t);
          if ("function" === typeof fn_) return fn_.apply(this, arguments);
        };
        return pt.apply(this, arguments);
      };
      return p;
    };

    window.XMLHttpRequest.prototype.open_dyva = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function(mtd, url) {
      if (!(/get_midroll_info/).test(url) && !((/^\/watch/).test(location.pathname) && (/get_video_info/).test(url))) {
        this.url_dyva = url;
        return this.open_dyva.apply(this, arguments);
      }
    };
    window.XMLHttpRequest.prototype.addEventListener_dyva = window.XMLHttpRequest.prototype.addEventListener;
    window.XMLHttpRequest.prototype.addEventListener = function(typ, fn) {
      if (typ === "readystatechange") {
        var f = fn;
        fn = function() {
          var z;
          if ((this.readyState === 4) && (/\/watch\?|get_video_info/).test(this.url_dyva)) {
            rpo = JSON.parse_dyva(this.responseText);
            try {
              rpo.forEach(p => {
                if (p.player && p.player.args && p.player.args.player_response) {
                  p.playerResponse_ = JSON.parse_dyva(p.player_response);
                  if (p.playerResponse_.playabilityStatus && (p.playerResponse_.playabilityStatus.status === "LOGIN_REQUIRED")) {
                    nav.navigate({commandMetadata: {webCommandMetadata: {url: location.href, webPageType: "WEB_PAGE_TYPE_BROWSE"}}}, false);
                    return;
                  }
                  patchPlayerResponse(p.playerResponse_);
                  p.player_response = JSON.stringify(p.playerResponse_);
                } else if (p.playerResponse) {
                  patchPlayerResponse(p.playerResponse);
                }
              });
            } catch(z) {}
          }
          return f.apply(this, arguments);
        };
      }
      return this.addEventListener_dyva.apply(this, arguments);
    };

    var img = window.Image, imgsrc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, "src");
    window.Imageq = function() {
      var r = new img();
      Object.defineProperty(r, "src", {
        get: function() {
          return imgsrc.get.apply(this, arguments);
        },
        set: function(v) {
          if (/\/pagead\/|\/ads\?/.test(v)) return v;
          return imgsrc.set.apply(this, arguments);
        }
      });
      return r;
    };

    window.Node.prototype.appendChild_dyva = window.Node.prototype.appendChild;
    window.Node.prototype.appendChild = function(node) {
      var a;
      if (!ipse && (a = document.querySelector('ytd-watch-flexy')) && (a = a.constructor.prototype) && a.isPlaShelfEnabled_) {
        a.isPlaShelfEnabled_ = () => false;
        ipse = true;
      }
      if ((!hca || !haia) && (a = document.querySelector('ytd-watch-next-secondary-results-renderer')) && (a = a.constructor.prototype)) {
        if (a.hasAllowedInstreamAd_ && !haia) {
          a.hasAllowedInstreamAd_ = () => false;
          haia = true;
        }
        if (a.hasCompanionAds_ && !hca) {
          a.hasCompanionAds_ = () => false;
          hca = true;
        }
      }
      if ((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && Array.from(node.childNodes).some((n, i) => {
        if (n.id === "masthead-ad") {
          n.remove();
          return true;
        }
      })); //window.Node.prototype.appendChild = window.Node.prototype.appendChild_dyva;
      if (node.querySelector && (a = node.querySelector('.ytp-ad-skip-button'))) a.click();
      return this.appendChild_dyva.apply(this, arguments);
    };

    function patchPlayerResponse(playerResponse) {
      if (playerResponse.adPlacements) playerResponse.adPlacements = [];
      if (playerResponse.playerAds) playerResponse.playerAds = [];
    }

    function patchPlayerArgs(args, a) {
      if (args.ad_device) args.ad_device = "0";
      if (args.ad_flags) args.ad_flags = 0;
      if (args.ad_logging_flag) args.ad_logging_flag = "0";
      if (args.ad_preroll) args.ad_preroll = "0";
      if (args.ad_slots) delete args.ad_slots;
      if (args.ad_tag) delete args.ad_tag;
      if (args.ad3_module) args.ad3_module = "0";
      if (args.adsense_video_doc_id) delete args.adsense_video_doc_id;
      if (args.afv) args.afv = false;
      if (args.afv_ad_tag) delete args.afv_ad_tag;
      if (args.allow_html5_ads) args.allow_html5_ads = 0;
      if (args.csi_page_type) args.csi_page_type = args.csi_page_type.replace(/watch7ad/, "watch7");
      if (args.enable_csi) args.enable_csi = "0";
      if (args.pyv_ad_channel) delete args.pyv_ad_channel;
      if (args.show_pyv_in_related) args.show_pyv_in_related = false;
      if (args.vmap) delete args.vmap;
      if (args.player_response) {
        a = JSON.parse_dyva(args.player_response);
        patchPlayerResponse(a);
        args.player_response = JSON.stringify(a);
      }
    }

    function patchSpf() {
      if (window.spf && !spf.request_dyva) {
        spf.request_dyva = spf.request;
        spf.request = function(a, b) {
          if (b && b.onDone) {
            var onDone_ = b.onDone;
            b.onDone = function(response) {
              var a = response;
              if (a && (/\/watch\?/).test(a.url) && (a = a.response) && (a = a.parts)) {
                a.forEach((p, a) => {
                  if (p.player && p.player.args && p.player.args.player_response) {
                    a = JSON.parse_dyva((p = p.player.args).player_response);
                    patchPlayerResponse(a);
                    p.player_response = JSON.stringify(a);
                  } else if (p.playerResponse) {
                    patchPlayerResponse(p.playerResponse);
                  }
                });
              }
              return onDone_.apply(this, arguments);
            };
          }
          return this.request_dyva.apply(this, arguments);
        };
        return;
      }
    }

    var ldh;

    function do1(ev, a) {

      if ((a = document.scripts[document.scripts.length - 1]) && /"adPlacements"/.test(a.text)) {
        a.text = a.text.replace(/"adPlacements"/, '"adPlacements":[],"zadPlacements"');
      }
      if (window.loadDataHook) {
        if (!window.loadDataHook.dyva) {
          ldh = window.loadDataHook;
          window.loadDataHook = function(ep, dt) {
            if (dt.playabilityStatus && (dt.playabilityStatus === "LOGIN_REQUIRED")) {
              location.href = location.href;
              throw "Ain't gonna login";
            }
            patchPlayerResponse(dt);
            return ldh.apply(this, arguments);
          };
          window.loadDataHook.dyva = true;
        }
      }
      if (window.ytcfg && window.ytcfg.set) {
        if (!window.ytcfg.set.dyva) {
          var ytcfgSet = window.ytcfg.set;
          window.ytcfg.set = function(ytConfig, ytValue){
            if (window.ytInitialPlayerResponse) {
              if (ytInitialPlayerResponse.playabilityStatus && (ytInitialPlayerResponse.playabilityStatus === "LOGIN_REQUIRED")) {
                location.href = location.href;
                throw "Ain't gonna login";
              }
              patchPlayerResponse(window.ytInitialPlayerResponse);
            }
            patchSpf();
            if (ytConfig) {
              var a;
              if (a = ytConfig.EXPERIMENT_FLAGS) {
                if (a.enable_auto_play_param_fix_for_masthead_ad) a.enable_auto_play_param_fix_for_masthead_ad = false;
                if (a.html5_check_both_ad_active_and_ad_info) a.html5_check_both_ad_active_and_ad_info = false;
                if (a.web_enable_ad_signals_in_it_context) a.web_enable_ad_signals_in_it_context = false;
              }
              if (ytConfig.SKIP_RELATED_ADS === false) ytConfig.SKIP_RELATED_ADS = true;
              if (ytConfig.TIMING_ACTION) ytConfig.TIMING_ACTION = ytConfig.TIMING_ACTION.replace(/watch7ad/, "watch7");
              if (a = ytConfig.TIMING_INFO) {
                if (a.yt_ad) a.yt_ad = 0;
                if (a.yt_ad_an) delete a.yt_ad_an;
                if (a.yt_ad_pr) a.yt_ad_pr = 0;
              }
              if (
                (a = ytConfig.WEB_PLAYER_CONTEXT_CONFIGS) && (a = a.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH) &&
                a.serializedExperimentFlags && a.serializedExperimentFlags.replace
              ) {
                a.serializedExperimentFlags = a.serializedExperimentFlags.replace(
                  /([a-z][^=]+)=([^&]+)/g, (s, a, b) => {
                    switch (a) {
                      case "enable_ad_break_end_time_on_pacf_tvhtml5": 
                      case "enable_auto_play_param_fix_for_masthead_ad":
                      case "html5_check_both_ad_active_and_ad_info": b = false; break;
                      case "web_enable_ad_signals_in_it_context":
                      case "web_player_gvi_wexit_adunit": b = false; break;
                    }
                    return a + "=" + b;
                  }
                );
              }
            }
            return ytcfgSet.apply(this, arguments);
          };
          window.ytcfg.set.dyva = true;
        }
      }
      if (window.yt) {
        if (window.yt.player && window.yt.player.Application) {
          if (window.yt.player.Application.create) {
            if (!window.yt.player.Application.create.dyva) {
              var ytPlayerApplicationCreate = window.yt.player.Application.create;
              window.yt.player.Application.create = function(id, ytPlayerConfig) {
                if ((id === "player-api") && ytPlayerConfig && ytPlayerConfig.args) {
                  if (ytPlayerConfig.args.raw_player_response) patchPlayerResponse(ytPlayerConfig.args.raw_player_response);
                  if (ytPlayerConfig.args.vmap) delete ytPlayerConfig.args.vmap;
                }
                return ytPlayerApplicationCreate.apply(this, arguments);
              };
              window.yt.player.Application.create.dyva = true;
            }
          }
          if (window.yt.player.Application.createAlternate) {
            if (!window.yt.player.Application.createAlternate.dyva) {
              var ytPlayerApplicationCreateAlternate = window.yt.player.Application.createAlternate;
              window.yt.player.Application.createAlternate = function(id, ytPlayerConfig) {
                if ((id === "player-api") && ytPlayerConfig && ytPlayerConfig.args) {
                  if (ytPlayerConfig.args.raw_player_response) patchPlayerResponse(ytPlayerConfig.args.raw_player_response);
                  if (ytPlayerConfig.args.vmap) delete ytPlayerConfig.args.vmap;
                }
                return ytPlayerApplicationCreateAlternate.apply(this, arguments);
              };
              window.yt.player.Application.createAlternate.dyva = true;
            }
          }
        }
        if (window.yt.setConfig) {
          if (!window.yt.setConfig.dyva) {
            var ytSetConfig = window.yt.setConfig;
            window.yt.setConfig = function(ytConfig){
              if (ytConfig && ytConfig.ADS_DATA) delete ytConfig.ADS_DATA;
              return ytSetConfig.apply(this, arguments);
            };
            window.yt.setConfig.dyva = true;
          }
        }
      }
      if (window.ytplayer && window.ytplayer.config && window.ytplayer.config.args) {
        if (!window.ytplayer.config.args.dvya) {
          patchPlayerArgs(window.ytplayer.config.args);
          window.ytplayer.config.args.dvya = true;
        }
      }
    }
    addEventListener(et, do1);
    if (et === "message") postMessage({});
    do1();

    addEventListener("spfpartprocess", function(ev) { //old youtube
      if (ev.detail && ev.detail.part && ev.detail.part.data &&
          ev.detail.part.data.swfcfg && ev.detail.part.data.swfcfg.args) {
        patchPlayerArgs(ev.detail.part.data.swfcfg.args);
      }
    }, true);

    addEventListener("load", a => {
      if (!(a = window.ayvp_cssOverride)) {
        a = document.createElement("STYLE");
        a.id = "ayvp_cssOverride";
        a.innerHTML = `\/*
.video-ads{display:none!important}
.ytp-ad-overlay-open .caption-window.ytp-caption-window-bottom{margin-bottom:4em}
.ytp-autohide .caption-window.ytp-caption-window-bottom, .ytp-hide-controls .caption-window.ytp-caption-window-bottom{margin-bottom:0!important}`;
        document.documentElement.appendChild(a);
      }
      if (et === "message") {
        if (document.readyState !== "complete") {
          postMessage({});
        } else removeEventListener(et, do1);
      }
    });
  };
  if ((GM_info || GM?.info)?.scriptHandler === 'Greasemonkey') { // Optional chaining FF74+, Chrome 80+
    // Greasemonkey workaround for compatibility with the original (unrestricted) Greasemonkey version.
    const e = document.createElement("SCRIPT");
    e.id = "dyvaUjs";
    e.text = "(" + fn + ")()";
    document.documentElement.appendChild(e);
  } else fn();

})();
EstherMoellman commented 3 years ago

@erosman ,

Your updated version works in all monkeys! Kudos! I'll share the info with the Dev. Thanks!

Simple question: I compared this your updated version with your previous one (yesterday), I saw the small changes in the code, and I wonder what is your recommendation for me and FM. Which version will be the most efficient for me and my FM?

Just FYI, in FM at line 366, the corrector shows in red 3 errors. Here is the image: https://imgur.com/a/BgPYuRj

erosman commented 3 years ago

Which version will be the most efficient for me and my FM?

Not a lot of difference. Use the last one which is more up-to-date,

Just FYI, in FM at line 366, the corrector shows in red 3 errors.

Optional Chaining (?.) was not supported in JSHint 2.12.0 (the script that generates the errors) (FM 2.0-2.30). Next release of FireMonkey 2.31 has the newly released JSHint 2.13.0 which supports (understands) Optional Chaining.

erosman commented 3 years ago

Since old GM doesn't support optional Chaining.. if the dev want GM3 compatibility, then the code could be written as

// ==UserScript==
// @name          Disable YouTube Video Ads
// @namespace     DisableYouTubeVideoAds
// @version       1.2.27a
// @license       AGPLv3
// @author        jcunews
// @website       https://greasyfork.org/en/users/85671-jcunews
// @description   Disable YouTube video & screen based ads at home page, and right before or in the middle of the main video playback. For new YouTube layout (Polymer) only.
// @match         https://www.youtube.com/*
// @grant         none
// @run-at        document-start
// @inject-into   page
// ==/UserScript==

(() => {

  let fn = (a, ipse, haia, hca, rpo, et) => {

    if ((a = document.scripts[document.scripts.length - 1]) && (a.id === "dyvaUjs")) a.remove();

    et = window.InstallTrigger ? "beforescriptexecute" : "message"; //Firefox workaround

    JSON.parse_dyva = JSON.parse;
    JSON.parse = function(a) {
      var m, z;
      if (rpo) {
        a = rpo; //JSON.parse_dyva(a); //from xhr/fetch
        try {
          if (a.forEach) {
            a.forEach((p, a) => {
              if (p.player && p.player.args && p.player.args.player_response) {
                a = p.player_response_; //JSON.parse_dyva(p.player_response);
                patchPlayerResponse(a); 
                p.player_response = JSON.stringify(a);
              } else if (p.playerResponse) {
                patchPlayerResponse(p.playerResponse);
              }
            });
          } else patchPlayerResponse(a);
        } catch(z) {}
        rpo = null;
      } else {
        a = JSON.parse_dyva(a);
        if (a.playerResponse) patchPlayerResponse(a.playerResponse);
      }
      return a;
    };

    var ftc = window.fetch_dyva = window.fetch;
    window.fetch = function(u) {
      if (u) {
        if (u.substr && /\/v1\/player\/ad_break/.test(u)) return new Promise(() => {});
        if (u.url && u.url.substr && /\/v1\/player\/ad_break/.test(u.url)) return new Promise(() => {});
      }
      return ftc.apply(this, arguments);
    };

    var rj = Response.prototype.json_dyva = Response.prototype.json;
    Response.prototype.json = function() {
      var rs = this, p = rj.apply(this, arguments), pt = p.then;
      p.then = function(fn) {
        var fn_ = fn;
        fn = function(j) {
          if (/\/v1\/player\?/.test(rs.url)) rpo = j;
          if ("function" === typeof fn_) return fn_.apply(this, arguments);
        };
        return pt.apply(this, arguments);
      };
      return p;
    };
    var rt = Response.prototype.text;
    Response.prototype.text = function() {
      var rs = this, p = rt.apply(this, arguments), pt = p.then;
      p.then = function(fn) {
        var fn_ = fn;
        fn = function(t) {
          if (/\/v1\/player\?/.test(rs.url)) rpo = JSON.parse_dyva(t);
          if ("function" === typeof fn_) return fn_.apply(this, arguments);
        };
        return pt.apply(this, arguments);
      };
      return p;
    };

    window.XMLHttpRequest.prototype.open_dyva = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function(mtd, url) {
      if (!(/get_midroll_info/).test(url) && !((/^\/watch/).test(location.pathname) && (/get_video_info/).test(url))) {
        this.url_dyva = url;
        return this.open_dyva.apply(this, arguments);
      }
    };
    window.XMLHttpRequest.prototype.addEventListener_dyva = window.XMLHttpRequest.prototype.addEventListener;
    window.XMLHttpRequest.prototype.addEventListener = function(typ, fn) {
      if (typ === "readystatechange") {
        var f = fn;
        fn = function() {
          var z;
          if ((this.readyState === 4) && (/\/watch\?|get_video_info/).test(this.url_dyva)) {
            rpo = JSON.parse_dyva(this.responseText);
            try {
              rpo.forEach(p => {
                if (p.player && p.player.args && p.player.args.player_response) {
                  p.playerResponse_ = JSON.parse_dyva(p.player_response);
                  if (p.playerResponse_.playabilityStatus && (p.playerResponse_.playabilityStatus.status === "LOGIN_REQUIRED")) {
                    nav.navigate({commandMetadata: {webCommandMetadata: {url: location.href, webPageType: "WEB_PAGE_TYPE_BROWSE"}}}, false);
                    return;
                  }
                  patchPlayerResponse(p.playerResponse_);
                  p.player_response = JSON.stringify(p.playerResponse_);
                } else if (p.playerResponse) {
                  patchPlayerResponse(p.playerResponse);
                }
              });
            } catch(z) {}
          }
          return f.apply(this, arguments);
        };
      }
      return this.addEventListener_dyva.apply(this, arguments);
    };

    var img = window.Image, imgsrc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, "src");
    window.Imageq = function() {
      var r = new img();
      Object.defineProperty(r, "src", {
        get: function() {
          return imgsrc.get.apply(this, arguments);
        },
        set: function(v) {
          if (/\/pagead\/|\/ads\?/.test(v)) return v;
          return imgsrc.set.apply(this, arguments);
        }
      });
      return r;
    };

    window.Node.prototype.appendChild_dyva = window.Node.prototype.appendChild;
    window.Node.prototype.appendChild = function(node) {
      var a;
      if (!ipse && (a = document.querySelector('ytd-watch-flexy')) && (a = a.constructor.prototype) && a.isPlaShelfEnabled_) {
        a.isPlaShelfEnabled_ = () => false;
        ipse = true;
      }
      if ((!hca || !haia) && (a = document.querySelector('ytd-watch-next-secondary-results-renderer')) && (a = a.constructor.prototype)) {
        if (a.hasAllowedInstreamAd_ && !haia) {
          a.hasAllowedInstreamAd_ = () => false;
          haia = true;
        }
        if (a.hasCompanionAds_ && !hca) {
          a.hasCompanionAds_ = () => false;
          hca = true;
        }
      }
      if ((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && Array.from(node.childNodes).some((n, i) => {
        if (n.id === "masthead-ad") {
          n.remove();
          return true;
        }
      })); //window.Node.prototype.appendChild = window.Node.prototype.appendChild_dyva;
      if (node.querySelector && (a = node.querySelector('.ytp-ad-skip-button'))) a.click();
      return this.appendChild_dyva.apply(this, arguments);
    };

    function patchPlayerResponse(playerResponse) {
      if (playerResponse.adPlacements) playerResponse.adPlacements = [];
      if (playerResponse.playerAds) playerResponse.playerAds = [];
    }

    function patchPlayerArgs(args, a) {
      if (args.ad_device) args.ad_device = "0";
      if (args.ad_flags) args.ad_flags = 0;
      if (args.ad_logging_flag) args.ad_logging_flag = "0";
      if (args.ad_preroll) args.ad_preroll = "0";
      if (args.ad_slots) delete args.ad_slots;
      if (args.ad_tag) delete args.ad_tag;
      if (args.ad3_module) args.ad3_module = "0";
      if (args.adsense_video_doc_id) delete args.adsense_video_doc_id;
      if (args.afv) args.afv = false;
      if (args.afv_ad_tag) delete args.afv_ad_tag;
      if (args.allow_html5_ads) args.allow_html5_ads = 0;
      if (args.csi_page_type) args.csi_page_type = args.csi_page_type.replace(/watch7ad/, "watch7");
      if (args.enable_csi) args.enable_csi = "0";
      if (args.pyv_ad_channel) delete args.pyv_ad_channel;
      if (args.show_pyv_in_related) args.show_pyv_in_related = false;
      if (args.vmap) delete args.vmap;
      if (args.player_response) {
        a = JSON.parse_dyva(args.player_response);
        patchPlayerResponse(a);
        args.player_response = JSON.stringify(a);
      }
    }

    function patchSpf() {
      if (window.spf && !spf.request_dyva) {
        spf.request_dyva = spf.request;
        spf.request = function(a, b) {
          if (b && b.onDone) {
            var onDone_ = b.onDone;
            b.onDone = function(response) {
              var a = response;
              if (a && (/\/watch\?/).test(a.url) && (a = a.response) && (a = a.parts)) {
                a.forEach((p, a) => {
                  if (p.player && p.player.args && p.player.args.player_response) {
                    a = JSON.parse_dyva((p = p.player.args).player_response);
                    patchPlayerResponse(a);
                    p.player_response = JSON.stringify(a);
                  } else if (p.playerResponse) {
                    patchPlayerResponse(p.playerResponse);
                  }
                });
              }
              return onDone_.apply(this, arguments);
            };
          }
          return this.request_dyva.apply(this, arguments);
        };
        return;
      }
    }

    var ldh;

    function do1(ev, a) {

      if ((a = document.scripts[document.scripts.length - 1]) && /"adPlacements"/.test(a.text)) {
        a.text = a.text.replace(/"adPlacements"/, '"adPlacements":[],"zadPlacements"');
      }
      if (window.loadDataHook) {
        if (!window.loadDataHook.dyva) {
          ldh = window.loadDataHook;
          window.loadDataHook = function(ep, dt) {
            if (dt.playabilityStatus && (dt.playabilityStatus === "LOGIN_REQUIRED")) {
              location.href = location.href;
              throw "Ain't gonna login";
            }
            patchPlayerResponse(dt);
            return ldh.apply(this, arguments);
          };
          window.loadDataHook.dyva = true;
        }
      }
      if (window.ytcfg && window.ytcfg.set) {
        if (!window.ytcfg.set.dyva) {
          var ytcfgSet = window.ytcfg.set;
          window.ytcfg.set = function(ytConfig, ytValue){
            if (window.ytInitialPlayerResponse) {
              if (ytInitialPlayerResponse.playabilityStatus && (ytInitialPlayerResponse.playabilityStatus === "LOGIN_REQUIRED")) {
                location.href = location.href;
                throw "Ain't gonna login";
              }
              patchPlayerResponse(window.ytInitialPlayerResponse);
            }
            patchSpf();
            if (ytConfig) {
              var a;
              if (a = ytConfig.EXPERIMENT_FLAGS) {
                if (a.enable_auto_play_param_fix_for_masthead_ad) a.enable_auto_play_param_fix_for_masthead_ad = false;
                if (a.html5_check_both_ad_active_and_ad_info) a.html5_check_both_ad_active_and_ad_info = false;
                if (a.web_enable_ad_signals_in_it_context) a.web_enable_ad_signals_in_it_context = false;
              }
              if (ytConfig.SKIP_RELATED_ADS === false) ytConfig.SKIP_RELATED_ADS = true;
              if (ytConfig.TIMING_ACTION) ytConfig.TIMING_ACTION = ytConfig.TIMING_ACTION.replace(/watch7ad/, "watch7");
              if (a = ytConfig.TIMING_INFO) {
                if (a.yt_ad) a.yt_ad = 0;
                if (a.yt_ad_an) delete a.yt_ad_an;
                if (a.yt_ad_pr) a.yt_ad_pr = 0;
              }
              if (
                (a = ytConfig.WEB_PLAYER_CONTEXT_CONFIGS) && (a = a.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH) &&
                a.serializedExperimentFlags && a.serializedExperimentFlags.replace
              ) {
                a.serializedExperimentFlags = a.serializedExperimentFlags.replace(
                  /([a-z][^=]+)=([^&]+)/g, (s, a, b) => {
                    switch (a) {
                      case "enable_ad_break_end_time_on_pacf_tvhtml5": 
                      case "enable_auto_play_param_fix_for_masthead_ad":
                      case "html5_check_both_ad_active_and_ad_info": b = false; break;
                      case "web_enable_ad_signals_in_it_context":
                      case "web_player_gvi_wexit_adunit": b = false; break;
                    }
                    return a + "=" + b;
                  }
                );
              }
            }
            return ytcfgSet.apply(this, arguments);
          };
          window.ytcfg.set.dyva = true;
        }
      }
      if (window.yt) {
        if (window.yt.player && window.yt.player.Application) {
          if (window.yt.player.Application.create) {
            if (!window.yt.player.Application.create.dyva) {
              var ytPlayerApplicationCreate = window.yt.player.Application.create;
              window.yt.player.Application.create = function(id, ytPlayerConfig) {
                if ((id === "player-api") && ytPlayerConfig && ytPlayerConfig.args) {
                  if (ytPlayerConfig.args.raw_player_response) patchPlayerResponse(ytPlayerConfig.args.raw_player_response);
                  if (ytPlayerConfig.args.vmap) delete ytPlayerConfig.args.vmap;
                }
                return ytPlayerApplicationCreate.apply(this, arguments);
              };
              window.yt.player.Application.create.dyva = true;
            }
          }
          if (window.yt.player.Application.createAlternate) {
            if (!window.yt.player.Application.createAlternate.dyva) {
              var ytPlayerApplicationCreateAlternate = window.yt.player.Application.createAlternate;
              window.yt.player.Application.createAlternate = function(id, ytPlayerConfig) {
                if ((id === "player-api") && ytPlayerConfig && ytPlayerConfig.args) {
                  if (ytPlayerConfig.args.raw_player_response) patchPlayerResponse(ytPlayerConfig.args.raw_player_response);
                  if (ytPlayerConfig.args.vmap) delete ytPlayerConfig.args.vmap;
                }
                return ytPlayerApplicationCreateAlternate.apply(this, arguments);
              };
              window.yt.player.Application.createAlternate.dyva = true;
            }
          }
        }
        if (window.yt.setConfig) {
          if (!window.yt.setConfig.dyva) {
            var ytSetConfig = window.yt.setConfig;
            window.yt.setConfig = function(ytConfig){
              if (ytConfig && ytConfig.ADS_DATA) delete ytConfig.ADS_DATA;
              return ytSetConfig.apply(this, arguments);
            };
            window.yt.setConfig.dyva = true;
          }
        }
      }
      if (window.ytplayer && window.ytplayer.config && window.ytplayer.config.args) {
        if (!window.ytplayer.config.args.dvya) {
          patchPlayerArgs(window.ytplayer.config.args);
          window.ytplayer.config.args.dvya = true;
        }
      }
    }
    addEventListener(et, do1);
    if (et === "message") postMessage({});
    do1();

    addEventListener("spfpartprocess", function(ev) { //old youtube
      if (ev.detail && ev.detail.part && ev.detail.part.data &&
          ev.detail.part.data.swfcfg && ev.detail.part.data.swfcfg.args) {
        patchPlayerArgs(ev.detail.part.data.swfcfg.args);
      }
    }, true);

    addEventListener("load", a => {
      if (!(a = window.ayvp_cssOverride)) {
        a = document.createElement("STYLE");
        a.id = "ayvp_cssOverride";
        a.innerHTML = `\/*
.video-ads{display:none!important}
.ytp-ad-overlay-open .caption-window.ytp-caption-window-bottom{margin-bottom:4em}
.ytp-autohide .caption-window.ytp-caption-window-bottom, .ytp-hide-controls .caption-window.ytp-caption-window-bottom{margin-bottom:0!important}`;
        document.documentElement.appendChild(a);
      }
      if (et === "message") {
        if (document.readyState !== "complete") {
          postMessage({});
        } else removeEventListener(et, do1);
      }
    });
  };
  if ((typeof GM_info === 'object' && GM_info.scriptHandler === 'Greasemonkey') || 
      (typeof GM === 'object' && GM.info && GM.info.scriptHandler === 'Greasemonkey')) {
    // Greasemonkey workaround for compatibility with the original (unrestricted) Greasemonkey version.
    const e = document.createElement("SCRIPT");
    e.id = "dyvaUjs";
    e.text = "(" + fn + ")()";
    document.documentElement.appendChild(e);
  } else fn();

})();