erosman / support

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

[FireMonkey] Userscript Compatibility #429

Closed erosman closed 1 year ago

erosman commented 2 years ago

FireMonkey 2.44 should be compatible with 98% of userscripts. Please read Help for more information.

Please post any userscript issues here for further investigation.

linsui commented 2 years ago

https://github.com/syhyz1990/baiduyun This doesn't work with FireMonkey. I guess you don't have a baiduyun account so if there is any info needed I'm more than happy to provide it. I guess GM_cookie is the problem. Thanks!

erosman commented 2 years ago

https://github.com/syhyz1990/baiduyun This doesn't work with FireMonkey. I guess you don't have a baiduyun account so if there is any info needed I'm more than happy to provide it. I guess GM_cookie is the problem. Thanks!

GM_cookie is used by 49 / 63,435 (0.07%) scripts and only supported by Tampermonkey. The code should work even without it. https://github.com/syhyz1990/baiduyun/blob/c8bdfc76656d2a00f4684fc462efbe35a0189b75/baiduyun.user.js#L525-L531

Has the developer commented about compatibility with FM? Does it work with GM or VM?

linsui commented 2 years ago

It works with VM but not with GM. In the README they say that VM or TM is required.

erosman commented 2 years ago

VM doesn't support GM_cookie, so that should not be the problem. At a glance, I dont see any issues. Have you asked them? If they point out the incompatibility, I will try to work on it.

linsui commented 2 years ago

I'll report this issue there and come back. Thanks!

Ghost-BD commented 2 years ago

https://greasyfork.org/en/scripts/432387-general-url-cleaner-revived doesn't work as intended. It might be a problem of how Firemonkey doesn't inject on all frames or whatever. Developer of this script is willing to make it Firemonkey compatible. ReCAPTCHA Solver, Hcaptcha Solver and other like them also victim of this. https://greasyfork.org/en/scripts/428651-tabview-youtube does not work with Firemonkey. https://greasyfork.org/en/scripts/433360-nova-youtube doesn't work before the last update but now looks like this load but doesn't show its icon in YouTube header. So, you can check it. https://greasyfork.org/en/scripts/405614-youtube-polymer-engine-fixes doesn't save user settings. https://greasyfork.org/en/scripts/429143-auto-set-youtube-volume can't set volume. Is there any option in Firemonkey to manually set value in Firemonkey like this script require? If not, I hope you will provide one. Because I have another problem with Nova YouTube that it stuck in loading options page with any user script manager in Firefox (might need to report there). So, I set its settings value manually in TM and VM but cannot in FM. Also, you said to consider UserCSS custom settings if there is a popular demand, then consider me as one. Firemonkey is an incredibly good user script and user styles manager. I like it and hope this to get recognition it deserves. No need to scratch your head over thus, just reported as you seem to seek to make Firemonky compatible with all user scripts which is very good. Spare me for my bad English. Thank you for your good work.

erosman commented 2 years ago

https://greasyfork.org/en/scripts/432387-general-url-cleaner-revived doesn't work as intended. It might be a problem of how Firemonkey doesn't inject on all frames or whatever. Developer of this script is willing to make it Firemonkey compatible.

Replied to the topic


ReCAPTCHA Solver, Hcaptcha Solver and other like them also victim of this.

At a glance, I dont see any issues. It has to be checked by the developers.


https://greasyfork.org/en/scripts/433360-nova-youtube doesn't work before the last update but now looks like this load but doesn't show its icon in YouTube header. So, you can check it.

At a glance, I dont see any issues. Too large to debug. Although not a problem ... dev may consider

// ==/UserScript==
/*jshint esversion: 6 */
window.nova_plugins = [];

Results in:

 'Optional chaining' is only available in ES11 (use 'esversion: 11').

Declares but never uses

// @grant           GM_addStyle
// @grant           GM_getResourceText
// @grant           GM_getResourceURL
// @grant           GM_addValueChangeListener
// @grant           GM_removeValueChangeListener
// @grant           GM_listValues
// @grant           GM_deleteValue
// @grant           GM_unregisterMenuCommand
// @grant           GM_openInTab

https://greasyfork.org/en/scripts/405614-youtube-polymer-engine-fixes doesn't save user settings.

At a glance, I dont see any issues. Does it work with Greasemonkey?

https://greasyfork.org/en/scripts/429143-auto-set-youtube-volume can't set volume. Is there any option in Firemonkey to manually set value in Firemonkey like this script require? If not, I hope you will provide one. Because I have another problem with Nova YouTube that it stuck in loading options page with any user script manager in Firefox (might need to report there). So, I set its settings value manually in TM and VM but cannot in FM.

Userscript should have the UI for the users tos et values. TM has the UI to view/update storage values, but that should not be a replaemnet for userscript's own UI.

Anyway, since the userscript is very small, users can manually set values here, but the code doesnt updates its value, so it has to be uninstalled and reinstalled.

GM_setValue('Default_Volume', 20); //Save the Default YT Volume as 20%

TBH, the code uses static value which makes GM_setValue/GM_getValue redundant. It is the same as:

// ==UserScript==
// @name         Auto Set Youtube Volume
// @namespace    YTVol
// @version      0.2
// @description  Choose the default volume for YouTube videos!
// @author       hacker09
// @match        https://www.youtube.com/embed/*
// @match        https://www.youtube.com/watch?v=*
// @icon         https://www.youtube.com/s/desktop/03f86491/img/favicon.ico
// @run-at       document-start
// ==/UserScript==

(function() {
  'use strict';
  const defaultVolume = 20;
  const data = {
    data: {
      volume: defaultVolume,
      muted: false,
      creation: Date.now()
    }
  };

  window.sessionStorage.setItem('yt-player-volume', JSON.stringify(data)); //Set the Default YT Volume
})();

User can manually change the 20 by editing the userscript.

:pushpin: AFA feature to edit userscript storage, let me think about it :thinking:


Also, you said to consider UserCSS custom settings if there is a https://github.com/erosman/support/issues/293, then consider me as one.

As per that topic, there is way to have custom setting by using @require to import them. You can also use @import in CSS which should work fine. Creating the User Interface will involve more work. let's see.

Ghost-BD commented 2 years ago

Sorry I might not be clear first time. For general URL cleaner revived, if you go any site where they use disqus.com for comment, FM doesn't inject that script where TM and VM do. In case of ReCAPTCHA Solver, when you trigger captcha in sites TM and VM run that script but not FM. This happens with similar scripts across different websites. So, reporting all of them to make Firemonkey compatible might not be a viable solution and all developers might not be friendly like you. Instead, if you inspect the problem in your spare time to make Firemonkey support most script might be better. General URL Cleaner Revived with FM . General URL Cleaner Revived with VM recaptcha with FM recaptcha with VM

https://greasyfork.org/en/scripts/428651-tabview-youtube doesn't work at all. https://greasyfork.org/en/scripts/405614-youtube-polymer-engine-fixes doesn't work properly. It doesn't act according to user setting instead always load default settings (try to change any setting). Looks like doesn't work with Greasemonkey either. At this point I can't say this script works properly with TM or VM 😊 as I don't use it anymore (use Nova YouTube now). "Userscript should have the UI for the users to set values."- Totally agree with you. As you see not all userscript have this and had to search long for that one to find as an example 😰. Thanks for your consideration to implement userscript storage. Yep, asking about UserCSS custom settings 'User Interface' if you can manage all of this. Thank you again.

erosman commented 2 years ago

For general URL cleaner revived, if you go any site where they use disqus.com for comment, FM doesn't inject that script where TM and VM do. In case of ReCAPTCHA Solver, when you trigger captcha in sites TM and VM run that script but not FM.

I see ... in that case, it seems to be the issue of @allFrames. FireMonkey by default adheres to Firefox API defaults which is to inject into top frame only, while GM|VM|TM by default inject into all frames. (for more info check Help under defaults).

In majority of cases, the top frame is enough and avoids extra overheads of injecting into sub-frames unnecessarily. However, captcha, disqus, etc are usually included as iframes. Therefore, userscript dealing with them should be set to inject into all frames.

Set @allFrames true in User Metadata and try.

Ghost-BD commented 2 years ago

Already tried that with URL cleaner from another issue you solved but doesn't work here. Solved ReCAPTCHA Solver though.

erosman commented 2 years ago

Already tried that with URL cleaner from another issue you solved but doesn't work here. Solved ReCAPTCHA Solver though.

URL cleaner issue doesn't relate to @allFrames as per my reply to the mentioned topic.

Ghost-BD commented 2 years ago

Ok then, hope he will solve the issue. Looking forward to seeing future feature implementation and better scripts compatibility in firemonkey. Thank you.

erosman commented 2 years ago

:pushpin: AFA feature to edit userscript storage, let me think about it :thinking:

v2.45
Added storage view/edit feature

lightinwater commented 2 years ago

https://greasyfork.org/en/scripts/7543-google-search-extra-buttons

Doesn't load unless explicitly asked to via the run command, probably something to do with run-on but beyond my ken I'm sorry

erosman commented 2 years ago

https://greasyfork.org/en/scripts/7543-google-search-extra-buttons Doesn't load unless explicitly asked to via the run command,

The patterns have issues e.g. https://www.google.*/* also matches https://www.google.*/search* so no need for https://www.google.*/search* https://encrypted.google.*/* also matches https://encrypted.google.*/search* so no need for https://encrypted.google.*/search*

I found the reason for not injecting which is due to the processing order when patterns like https://www.google.*/* are used. I will fix it in v2.45.

For now, if you only use google.com, add this to the User Metadata

@disable-include
@match      *://www.google.com/*
@match      https://encrypted.google.com/*
@match      https://spmbt.github.io/googleSearchExtraButtons/saveYourLocalStorage.html
@match      https://www.gstatic.com/sites/p/b9356d/system/services/test.html
@match      https://www.gstatic.com/index.html
zurlafaga commented 2 years ago

https://greasyfork.org/en/scripts/436115-return-youtube-dislike

The userscript is not working in FireMonkey v2.44.

erosman commented 2 years ago

https://greasyfork.org/en/scripts/436115-return-youtube-dislike

The userscript in not working in FireMonkey v2.44.

Unfortunately it is due to the CORS restriction Bug 1715249 in userScript context when it is using fetch on line 219.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://returnyoutubedislikeapi.com/votes?videoId=DkYQksqpFyg. (Reason: CORS request did not succeed). Status code: (null).

GM.fetch/GM.xmlHttpRequest would work if developers decides to update it. I will see if I can work out anything until the Firefox bug is fixed.

zurlafaga commented 2 years ago

Unfortunately it is due to the CORS restriction Bug 1715249 in userScript context when it is using fetch on line 219.

Just for reference, ViolentMonkey and TamperMonkey are working with the script at https://greasyfork.org/en/scripts/436115-return-youtube-dislike

erosman commented 2 years ago

Just for reference, ViolentMonkey and TamperMonkey are working with the script at https://greasyfork.org/en/scripts/436115-return-youtube-dislike

GM|TM|VM inject into content context while FireMonkey injects into secure userScript context. The bug relates to userScript context.

Note: Script declares @grant GM.xmlHttpRequest but never uses it.

Here is the same script, with added compatibility with FireMonkey as well (line 219-234 no other changes):

// ==UserScript==
// @name         Return YouTube Dislike
// @namespace    https://www.returnyoutubedislike.com/
// @homepage     https://www.returnyoutubedislike.com/
// @version      0.9.0
// @encoding     utf-8
// @description  Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/
// @icon         https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png
// @author       Anarios & JRWR
// @match        *://*.youtube.com/*
// @exclude      *://music.youtube.com/*
// @exclude      *://*.music.youtube.com/*
// @compatible   chrome
// @compatible   firefox
// @compatible   opera
// @compatible   safari
// @compatible   edge
// @grant        GM.xmlHttpRequest
// @connect      youtube.com
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==
const LIKED_STATE = "LIKED_STATE";
const DISLIKED_STATE = "DISLIKED_STATE";
const NEUTRAL_STATE = "NEUTRAL_STATE";
let previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL
let likesvalue = 0;
let dislikesvalue = 0;

let isMobile = location.hostname == "m.youtube.com";
let mobileDislikes = 0;
function cLog(text, subtext = "") {
  subtext = subtext.trim() === "" ? "" : `(${subtext})`;
  console.log(`[Return YouTube Dislikes] ${text} ${subtext}`);
}

function getButtons() {
  if (isMobile) {
    return document.querySelector(".slim-video-action-bar-actions");
  }
  if (document.getElementById("menu-container")?.offsetParent === null) {
    return document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div");
  } else {
    return document
      .getElementById("menu-container")
      ?.querySelector("#top-level-buttons-computed");
  }
}

function getLikeButton() {
  return getButtons().children[0];
}

function getDislikeButton() {
  return getButtons().children[1];
}

function isVideoLiked() {
  if (isMobile) {
    return (
      getLikeButton().querySelector("button").getAttribute("aria-label") ==
      "true"
    );
  }
  return getLikeButton().classList.contains("style-default-active");
}

function isVideoDisliked() {
  if (isMobile) {
    return (
      getDislikeButton().querySelector("button").getAttribute("aria-label") ==
      "true"
    );
  }
  return getDislikeButton().classList.contains("style-default-active");
}

function isVideoNotLiked() {
  if (isMobile) {
    return !isVideoLiked();
  }
  return getLikeButton().classList.contains("style-text");
}

function isVideoNotDisliked() {
  if (isMobile) {
    return !isVideoDisliked();
  }
  return getDislikeButton().classList.contains("style-text");
}

function checkForUserAvatarButton() {
  if (isMobile) {
    return;
  }
  if (document.querySelector('#avatar-btn')) {
    return true
  } else {
    return false
  }
}

function getState() {
  if (isVideoLiked()) {
    return LIKED_STATE;
  }
  if (isVideoDisliked()) {
    return DISLIKED_STATE;
  }
  return NEUTRAL_STATE;
}

function setLikes(likesCount) {
  if (isMobile) {
    getButtons().children[0].querySelector(".button-renderer-text").innerText =
      likesCount;
    return;
  }
  getButtons().children[0].querySelector("#text").innerText = likesCount;
}

function setDislikes(dislikesCount) {
  if (isMobile) {
    mobileDislikes = dislikesCount;
    return;
  }
  getButtons().children[1].querySelector("#text").innerText = dislikesCount;
}

(typeof GM_addStyle != "undefined"
  ? GM_addStyle
  : (styles) => {
      let styleNode = document.createElement("style");
      styleNode.type = "text/css";
      styleNode.innerText = styles;
      document.head.appendChild(styleNode);
    })(`
    #return-youtube-dislike-bar-container {
      background: var(--yt-spec-icon-disabled);
      border-radius: 2px;
    }

    #return-youtube-dislike-bar {
      background: var(--yt-spec-text-primary);
      border-radius: 2px;
      transition: all 0.15s ease-in-out;
    }

    .ryd-tooltip {
      position: relative;
      display: block;
      height: 2px;
      top: 9px;
    }

    .ryd-tooltip-bar-container {
      width: 100%;
      height: 2px;
      position: absolute;
      padding-top: 6px;
      padding-bottom: 28px;
      top: -6px;
    }
  `);

function createRateBar(likes, dislikes) {
  if (isMobile) {
    return;
  }
  let rateBar = document.getElementById("return-youtube-dislike-bar-container");

  const widthPx =
    getButtons().children[0].clientWidth +
    getButtons().children[1].clientWidth +
    8;

  const widthPercent =
    likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;

  if (!rateBar && !isMobile) {
    document.getElementById("menu-container").insertAdjacentHTML(
      "beforeend",
      `
        <div class="ryd-tooltip" style="width: ${widthPx}px">
        <div class="ryd-tooltip-bar-container">
           <div
              id="return-youtube-dislike-bar-container"
              style="width: 100%; height: 2px;"
              >
              <div
                 id="return-youtube-dislike-bar"
                 style="width: ${widthPercent}%; height: 100%"
                 ></div>
           </div>
        </div>
        <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
           <!--css-build:shady-->${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}
        </tp-yt-paper-tooltip>
        </div>
`
    );
  } else {
    document.getElementById(
      "return-youtube-dislike-bar-container"
    ).style.width = widthPx + "px";
    document.getElementById("return-youtube-dislike-bar").style.width =
      widthPercent + "%";

    document.querySelector(
      "#ryd-dislike-tooltip > #tooltip"
    ).innerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
  }
}

function setState() {
  cLog("Fetching votes...");
  let statsSet = false;

  if (GM_info.scriptHandler === 'FireMonkey') {
    GM.fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`, {responseType: 'json'})
    .then((response) => response.json)
    .then((json) => {
      if (json && !("traceId" in json) && !statsSet) {
        const { dislikes, likes } = json;
        cLog(`Received count: ${dislikes}`);
        likesvalue = likes;
        dislikesvalue = dislikes;
        setDislikes(numberFormat(dislikes));
        createRateBar(likes, dislikes);
      }
    });
    setState = function(){};
    return;
  }

  fetch(
    `https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`
  ).then((response) => {
    response.json().then((json) => {
      if (json && !("traceId" in response) && !statsSet) {
        const { dislikes, likes } = json;
        cLog(`Received count: ${dislikes}`);
        likesvalue = likes;
        dislikesvalue = dislikes;
        setDislikes(numberFormat(dislikes));
        createRateBar(likes, dislikes);
      }
    });
  });
  setState = function(){};
}

function likeClicked() {
  if (checkForUserAvatarButton() == true) {
    if (previousState == 1) {
      likesvalue--;
      createRateBar(likesvalue, dislikesvalue);
      setDislikes(numberFormat(dislikesvalue));
      previousState = 3
    } else if (previousState == 2) {
      likesvalue++;
      dislikesvalue--;
      setDislikes(numberFormat(dislikesvalue))
      createRateBar(likesvalue, dislikesvalue);
      previousState = 1
    } else if (previousState == 3) {
      likesvalue++;
      createRateBar(likesvalue, dislikesvalue)
      previousState = 1
    }
  }
}

function dislikeClicked() {
  if (checkForUserAvatarButton() == true) {
    if (previousState == 3) {
      dislikesvalue++;
      setDislikes(numberFormat(dislikesvalue));
      createRateBar(likesvalue, dislikesvalue);
      previousState = 2
    } else if (previousState == 2) {
      dislikesvalue--;
      setDislikes(numberFormat(dislikesvalue));
      createRateBar(likesvalue, dislikesvalue);
      previousState = 3
    } else if (previousState == 1) {
      likesvalue--;
      dislikesvalue++;
      setDislikes(numberFormat(dislikesvalue));
      createRateBar(likesvalue, dislikesvalue);
      previousState = 2
    }
  }
}

function setInitialState() {
  setState();
}

function getVideoId() {
  const urlObject = new URL(window.location.href);
  const pathname = urlObject.pathname;
  if (pathname.startsWith("/clip")) {
    return document.querySelector("meta[itemprop='videoId']").content;
  } else {
    return urlObject.searchParams.get("v");
  }
}

function isVideoLoaded() {
  if (isMobile) {
    return document.getElementById("player").getAttribute("loading") == "false";
  }
  const videoId = getVideoId();

  return (
    document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null
  );
}

function roundDown(num) {
  if (num < 1000) return num;
  const int = Math.floor(Math.log10(num) - 2);
  const decimal = int + (int % 3 ? 1 : 0);
  const value = Math.floor(num / 10 ** decimal);
  return value * 10 ** decimal;
}

function numberFormat(numberState) {
  let userLocales;
  try {
    userLocales = new URL(
      Array.from(document.querySelectorAll("head > link[rel='search']"))
        ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
        ?.getAttribute("href")
    )?.searchParams?.get("locale");
  } catch {}
  const formatter = Intl.NumberFormat(
    document.documentElement.lang || userLocales || navigator.language,
    {
      notation: "compact",
    }
  );

  return formatter.format(roundDown(numberState));
}

function setEventListeners(evt) {
  let jsInitChecktimer;

  function checkForJS_Finish(check) {
    console.log();
    if (getButtons()?.offsetParent && isVideoLoaded()) {
      clearInterval(jsInitChecktimer);
      const buttons = getButtons();

      if (!window.returnDislikeButtonlistenersSet) {
        cLog("Registering button listeners...");
        buttons.children[0].addEventListener("click", likeClicked);
        buttons.children[1].addEventListener("click", dislikeClicked);
        window.returnDislikeButtonlistenersSet = true;
      }
      setInitialState();
    }
  }

  cLog("Setting up...");
  jsInitChecktimer = setInterval(checkForJS_Finish, 111);
}

(function () {
  "use strict";
  window.addEventListener("yt-navigate-finish", setEventListeners, true);
  setEventListeners();
})();
if (isMobile) {
  let originalPush = history.pushState;
  history.pushState = function (...args) {
    window.returnDislikeButtonlistenersSet = false;
    setEventListeners(args[2]);
    return originalPush.apply(history, args);
  };
  setInterval(() => {
    getDislikeButton().querySelector(".button-renderer-text").innerText =
      mobileDislikes;
  }, 1000);
}
ivysrono commented 2 years ago

I write some scripts that use GM_openInTab and window.focus and window.close: GM_openIntab return the opened tab (A), addEventLinstener to focus A and close it auto. I'm using Violentmonkey, I know FireMonkey don't plan to suppot window.focus and window.close, however, what can I do?

erosman commented 2 years ago

I write some scripts that use GM_openInTab and window.focus and window.close: GM_openIntab return the opened tab (A), addEventLinstener to focus A and close it auto. I'm using Violentmonkey, I know FireMonkey don't plan to suppot window.focus and window.close, however, what can I do?

GM_openIntab -> focus
What focus does it to make the tab active. GM.openInTab(url) creates a tab and makes it active. Unless the script wants to make tab active later, or on some condition, that should do the job.

window.close I have updated GM.openIntab/GM_openIntab which should allow JavaScript window.close() to work when the API is used. It will be in v2.45 It is not fully tested but I tried it with the following test userscript and it works.

// ==UserScript==
// @name          window.close test
// @match         *://*.example.com/*
// @match         *://github.com/*
// ==/UserScript==

if (location.hostname.endsWith('github.com')) {
  window.setTimeout(() => GM.openInTab('https://example.com/'), 2000);
}
else {
  window.setTimeout(() => window.close(), 2000);
}
ivysrono commented 2 years ago

Unless the script wants to make tab active later

Yes, need later.

zurlafaga commented 2 years ago

https://greasyfork.org/en/scripts/428651-tabview-youtube

The userscript is not working in FireMonkey v2.45.

erosman commented 2 years ago

https://greasyfork.org/en/scripts/428651-tabview-youtube

The userscript is not working in FireMonkey v2.45.

At first glance, I don't see any issues. The script to too large and has large minified @require to debug properly.

If the developer points out the issue, I will see what can be done.

PS. I posted to Return YouTube Dislike with the code but there is no response so far.

zurlafaga commented 2 years ago

@erosman,

The script at https://greasyfork.org/en/scripts/436115-return-youtube-dislike with your changes is working in FireMonkey v2.45. Thank you.

Just for reference, ViolentMonkey and TamperMonkey are working with the script at https://greasyfork.org/en/scripts/428651-tabview-youtube

Ghost-BD commented 2 years ago

https://greasyfork.org/en/scripts/419825-opera-browser-rocker-mouse-gestures-search-highlight script does not work as expected. Units and currency converters of this script not working with FM. You can use this page to check currency conversion. Thanks.

erosman commented 2 years ago

https://greasyfork.org/en/scripts/419825-opera-browser-rocker-mouse-gestures-search-highlight script does not work as expected. Units and currency converters of this script not working with FM. You can use this page to check currency conversion. Thanks.

It could the be due to Bug 1715249 as the script uses fetch.

I am waiting for the bug to be fixed.

Ghost-BD commented 2 years ago

Maybe. But no CORS errors are displayed in the console and enabling CORS with extension doesn't help. Also i do not understand a thing about those (or any of those hard code you write😯). So I am leaving all the hard work upon you🤣. Thanks again.

erosman commented 2 years ago

If there is no CORS error, then I didn't notice anything else. I tried it a bit but the script is too large to debug. If the dev point out the issue, I can work on it.

Ghost-BD commented 2 years ago

He specifically mentioned "Scripts for tampermonkey". Works fine with VM. I will ask him in greasyfork if he can help.

erosman commented 2 years ago

Actually, I get an error with that script when selecting a currently. The popup comes up but doesn't show exchange. Note: TM alters page CSP so you wont get that error with TM.

TypeError: NetworkError when attempting to fetch resource. line 332

When trying it directly e.g.

fetch('https://api.allorigins.win/get?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3D205%20EUR%20in%20CAD')
.then(response => response.json())
.then(data => console.log(data))
.catch(console.error);

GET https://api.allorigins.win/get?url=https://www.google.com/search?q=205 EUR in CAD TypeError: NetworkError when attempting to fetch resource. Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.allorigins.win/get?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3D205%20EUR%20in%20CAD. (Reason: CORS request did not succeed). Status code: (null). Content Security Policy: The page’s settings blocked the loading of a resource at https://api.allorigins.win/get?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3D205%20EUR%20in%20CAD (“default-src”).

Ghost-BD commented 2 years ago

Looks like some fetch related CORS error indeed. Unit converter works fine only problem is to convert currency cause script needs to fetch data from google. Here is what the dev had to say about this. Might have to wait for Mozilla to fix Firefox bug. Thanks.

erosman commented 2 years ago

I probably will not waste time changing the script from fetch to XMLHttpRequest just for the script to work on that extension.

Ghost-BD commented 2 years ago

The issue of CORS affects all userscript managers (FM|GM|VM) except TM since TM changes page CSP

GM|TM|VM inject into content context while FireMonkey injects into secure userScript context. The bug relates to userScript context.

I was under the impression that CORS only affect scripts running using userScripts API as you mentioned previously and also VM runs the script fine.

CORS on this page affects the XHR to https://api.allorigins.win/ (nothing to do with google)

For this purpose, https://api.allorigins.win/ is doing exactly what GM_xmlhttpRequest does

Pull contents from any page via API (as JSON/P or raw) and avoid Same-origin policy problems.

If I understood correctly https://api.allorigins.win/ solution plagued itself with same problem it try to solve.

Changing page CSP is not allowed by Mozilla

I thought CSP is some form of telemetry for web sites also there is an option to disable CSP reporting in ublock origin.

erosman commented 2 years ago

I was under the impression that CORS only affect scripts running using userScripts API as you mentioned previously and also VM runs the script fine.

CORS is a safeguard that affects all HTTP comminations. The issue of userScripts context bug adds to the complications.

Above measures circumvent CORS protection against XSS. The difference with GM_xmlhttpRequest API is that it is less visible to users.

CORS is good for security

CORS is there to safeguard users' security. For example, CORS prevents a userscript that runs everywhere (too many of those with @include *), to run in users' bank login page and grab user/pass and send it to a remote location.

Cross-Origin Resource Sharing (CORS) For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, XMLHttpRequest and the Fetch API follow the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.

CPS is good for security

Content Security Policy (CSP) Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution.

For best security, CORS & CSP should not be bypassed.

If I understood correctly https://api.allorigins.win/ solution plagued itself with same problem it try to solve.

Above API aims to bypass CORS restrictions for scripts, same as GM_xmlhttpRequest.

I thought CSP is some form of telemetry for web sites also there is an option to disable CSP reporting in ublock origin.

Not at all .... Users sometimes want to bypass CORS as some features that they want might not be possible (e.g. some userscripts), however that is their own personal choice.

Block CSP reports blocks the feedback of the browser response on CSP violation. It does not block the CSP itself.

CORS & CSP

Ghost-BD commented 2 years ago

Thanks for detailed explanation of CORS & CSP. As it stands, TM alters CSP and VM temper CORS somehow. Best way for background HTTP request is using GM_xmlhttpRequest which mitigate CORS error. FM also support fetch but now bugged. If it get more complicated and technical then this that will surely go over my head. So if script author don't want to change from fetch to GM_xmlhttpRequest that's fine as FM supports both.

erosman commented 2 years ago

FM also support fetch but now bugged.

fetch is standard JavaScript. All browsers, extension, userscripts use and support JavaScript. Bug 1715249 is related to CORS. ATM, standard fetch in userScripts context (FM) is more restrictive than standard fetch in content context (GM|TM|VM).

So if script author don't want to change from fetch to GM_xmlhttpRequest that's fine as FM supports both.

Standard fetch or xmlHttpRequest vs GM.fetch or GM.xmlHttpRequest

Ghost-BD commented 2 years ago

That means currency conversion option of that script out reach for now. Maybe I shall ask him one more time to use GM_xmlhttpRequest|GM.xmlHttpRequest as this will only leads better compatibility. Looked at Return YouTube Dislike, doesn't need GM.fetch if @inject-into page user metadata used. So FM can also inject scripts in page context like VM? But same isn't true for Opera Browser Rocker. It gets totally broken with @inject-into page. Now if I use your modified Return YouTube Dislike with GM.fetch, then there is no update to newer version. So this is not a permanent solution until script author support the change. We can not force them to do that. You are doing a great work considering the limitations in this area. Here is another script that does not work with FM. Take your time, i don't use it. Thank you.

erosman commented 2 years ago

Maybe I shall ask him one more time to use GM_xmlhttpRequest|GM.xmlHttpRequest as this will only leads better compatibility.

GM_xmlhttpRequest|GM.xmlHttpRequest is supported by all userscript managers

So FM can also inject scripts in page context like VM?

Yes.. but VM allows GM API in page context while FM & TM don't. If a script doesn't use any GM API, then that option can be used.

Here is a copy of Opera Browser Rocker+Mouse that works with FireMonkey as well as TM & VM. I only updated the fetch lines 322-325 (1.5 lines of extra code)

// ==UserScript==
// @name               Opera Browser Rocker+Mouse Gestures + Search HighLight
// @namespace          OperaBrowserGestures
// @description        This script works on any browser and simulates the Opera Browser Mouse+Rocker Gestures, along with the Search HighLight and Units+Currency Converters, but with this script you can modify or disable them as you want.
// @version            0.0.48
// @author             hacker09
// @include            *
// @icon               https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.opera.com/&size=64
// @require            https://code.jquery.com/jquery-3.5.1.min.js
// @grant              GM_registerMenuCommand
// @grant              GM_openInTab
// @grant              window.close
// @grant              GM_setValue
// @grant              GM_getValue
// @run-at             document-end
// ==/UserScript==

// *** Mouse Gesture Settings Below *****************************************************************************************************************************************
GM_registerMenuCommand("Enable/Disable Mouse Gestures", MouseGestures); //Adds an option to the tampermonkey menu
if (GM_getValue("MouseGestures") !== true && GM_getValue("MouseGestures") !== false) { //If the value doesn't exist define as true
  GM_setValue("MouseGestures", true); //Defines the variable as true
} //Finishes the if condition

function MouseGestures() //Function to enable or disable the MouseGestures
{ //Starts the function MouseGestures
  if (GM_getValue("MouseGestures") === true) { //If the last config was true, set as false
    GM_setValue("MouseGestures", false); //Defines the variable as false
  } //Finishes the if condition
  else { //If the last config was false, set as true
    GM_setValue("MouseGestures", true); //Defines the variable as true
    location.reload(); //Reloads the page
  } //Finishes the else condition
} //Finishes the function MouseGestures

if (GM_getValue("MouseGestures") === true) //If the MouseGestures is enabled
{ //Starts the if condition
  const SENSITIVITY = 3; // Adjust the script mouse senvity here between 1 ~ 5
  const TOLERANCE = 3; // Adjust the script mouse tolerance here between  1 ~ 5

  const funcs = { //Variable to store the functions

    'L': function() { //Function that will run when the mouse movement Left is performed
      window.history.back(); //Go Back
    }, //Finishes the mouse movement Left

    'R': function() { //Function that will run when the mouse movement Right is performed
      window.history.forward(); //Go Forward
    }, //Finishes the mouse movement Right

    'D': function() { //Function that will run when the mouse movement Down is performed
      if (IsShiftNotPressed === true) { //If the shift key isn't being pressed
        GM_openInTab(link, { //Open the link on a new tab
          active: true, //Focus on the new tab
          insert: true, //Insert the new tab after the actual tab
          setParent: true //Return to the tab the user was in
        }); //Open the link that was hovered
      } //Finishes the if condition
    }, //Finishes the mouse movement Down

    'UD': function() { //Function that will run when the mouse movement Up+Down is performed
      window.location.reload(); //Reload the Tab
    }, //Finishes the mouse movement Up+Down

    'DR': function(e) { //Function that will run when the mouse movement Down+Right is performed
      window.top.close(); //Close the tab
      e.preventDefault(); //Prevent the default context menu from being opened
      e.stopPropagation(); //Prevent the default context menu from being opened
    }, //Finishes the mouse movement Down+Right

    'DU': function() { //Function that will run when the mouse movement Down+Up is performed
      GM_openInTab(link, { //Open the link that was hovered
        active: false, //Don't focus on the new tab
        insert: true, //Insert the new tab after the actual tab
        setParent: true //Return to the tab the user was in
      }); //Open the link that was hovered on a new background tab
    } //Finishes the mouse movement Down+Up

  }; //Finishes the variable to store the functions

  // *** Below this line is the math codes that track the mouse movement gestures *******************************************************************************************
  const s = 1 << ((7 - SENSITIVITY) << 1);
  const t1 = Math.tan(0.15708 * TOLERANCE),
    t2 = 1 / t1;

  let x, y, path;

  const tracer = function(e) { //Starts the const tracer
    let cx = e.clientX,
      cy = e.clientY,
      deltaX = cx - x,
      deltaY = cy - y,
      distance = deltaX * deltaX + deltaY * deltaY;
    if (distance > s) {
      let slope = Math.abs(deltaY / deltaX),
        direction = '';
      if (slope > t1) {
        direction = deltaY > 0 ? 'D' : 'U';
      } else if (slope <= t2) {
        direction = deltaX > 0 ? 'R' : 'L';
      }
      if (path.charAt(path.length - 1) !== direction) {
        path += direction;
      }
      x = cx;
      y = cy;
    }
  }; //Finishes the const tracer

  window.addEventListener('mousedown', function(e) { //Add an advent listener to the page to detect when the mouse is clicked
    if (e.which === 3) { //Starts the if condition
      x = e.clientX;
      y = e.clientY;
      path = "";
      window.addEventListener('mousemove', tracer, false); //Add an advent listener to the page to detect the mouse position
    } //Finishes the if condition
  }, false); //Finishes the advent listener

  var IsShiftNotPressed = true; //Variable to hold the shift key status
  window.addEventListener("contextmenu", function(e) { //Adds an advent listener to the page to know when the shift key is pressed or not
    if (e.shiftKey) { //If the shift key was pressed
      IsShiftNotPressed = false; //Variable to hold the shift key status
      window.open(link, '_blank', 'height=' + window.screen.height + ',width=' + window.screen.width); //Open the link on a new window
    } //Finishes the if condition
    if (LeftClicked === true) { //If the Left Click was released when the Rocker Mouse Gestures are activated
      e.preventDefault(); //Prevent the default context menu from being opened
      e.stopPropagation(); //Prevent the default context menu from being opened
    } //Finishes the if condition
    setTimeout(function() { //Starts the settimeout function
      IsShiftNotPressed = true; //Variable to hold the shift key status
    }, 500); //Finishes the settimeout function
  }, false); //Finishes the advent listener

  window.addEventListener('contextmenu', function(e) { //When the right click button is released
    window.removeEventListener('mousemove', tracer, false); //Stop tracking the mouse movements
    if (path !== "") { //Starts the if condition
      e.preventDefault(); //Prevent the default context menu from being opened
      if (funcs.hasOwnProperty(path)) { //Starts the if condition
        funcs[path]();
      } //Finishes the if condition
    } //Finishes the if condition
  }, false); //Finishes the advent listener

  var link; //Make the variable global
  Array.from(document.querySelectorAll('a')).forEach(Element => Element.onmouseover = function() { //Get all the a link elements and add an advent listener to the link element
    link = this.href; //Store the actual hovered link to a variable
  }); //Finishes the forEach

  Array.from(document.querySelectorAll('a')).forEach(Element => Element.onmouseout = function() { //Get all the a link elements and add an advent listener to the link element
    var PreviousLink = link; //Save the actual hovered link to another variable
    setTimeout(function() { //Starts the settimeout function
      if (PreviousLink === link) //If he actual hovered link is still the same as the Previously hovered Link
      { //Starts the if condition
        link = 'about:newtab'; //Make the script open a new browser tab when the mouse leaves any link that was hovered
      } //Finishes the if condition
    }, 200); //Finishes the settimeout function
  }); //Finishes the forEach

} //Finishes the if condition

// *** Rocker Mouse Gesture Settings Below ***********************************************************************************************************************************
GM_registerMenuCommand("Enable/Disable Rocker Mouse Gestures", RockerMouseGestures); //Adds an option to the tampermonkey menu
if (GM_getValue("RockerMouseGestures") !== true && GM_getValue("RockerMouseGestures") !== false) { //If the value doesn't exist define as false
  GM_setValue("RockerMouseGestures", false); //Defines the variable as false
} //Finishes the if condition

function RockerMouseGestures() //Function to enable or disable the RockerMouseGestures
{ //Starts the function RockerMouseGestures
  if (GM_getValue("RockerMouseGestures") === true) { //If the last config was true, set as false
    GM_setValue("RockerMouseGestures", false); //Defines the variable as false
  } //Finishes the if condition
  else { //If the last config was false, set as true
    GM_setValue("RockerMouseGestures", true); //Defines the variable as true
    location.reload(); //Reloads the page
  } //Finishes the else condition
} //Finishes the function RockerMouseGestures

if (GM_getValue("RockerMouseGestures") === true || GM_getValue("SearchHiLight") === true) //If the RockerMouseGestures or the SearchHiLight is enabled
{ //Starts the if condition
  var LeftClicked, RightClicked; //Make these variables global
  window.addEventListener("mousedown", function(e) { //Detect the right and left mouse clicks presses on the page
    switch (e.button) { //Start the switch condition
      case 0: //If Left Click was Pressed
        LeftClicked = true; //Set the variable LeftClicked as true
        break; //Don't execute the lines below if the Left Key was Pressed
      case 2: //If Right Click was Pressed
        RightClicked = true; //Set the variable RightClicked as true
        break; //Don't execute the lines below if the Right Key was Pressed
    } //Finishes the switch condition
  }, false); //Finishes the adventlistener mousedown

  window.addEventListener("mouseup", function(e) { //Detect the right and left mouse clicks releases on the page
    switch (e.button) { //Start the switch condition
      case 0: //If Left Click was released
        LeftClicked = false; //Set the variable LeftClicked as false
        break; //Don't execute the lines below if the Left Key was Pressed
      case 2: //If Right Click was released
        RightClicked = false; //Set the variable RightClicked as false
        break; //Don't execute the lines below if the Left Key was Pressed
    } //Finishes the switch condition
    if (LeftClicked && RightClicked === false) { //If Left was Clicked and then Right Click was released
      window.history.back(); //Go Back
    } //Finishes the if condition
    if (RightClicked && LeftClicked === false) { //If Right was Clicked and then Left Click was released
      window.history.forward(); //Go Forward
    } //Finishes the if condition
  }, false); //Finishes the adventlistener mouseup
} //Finishes the if condition

// *** SearchHighLight Below *************************************************************************************************************************************************
GM_registerMenuCommand("Enable/Disable SearchHiLight", SearchHiLight); //Adds an option to the tampermonkey menu
if (GM_getValue("SearchHiLight") !== true && GM_getValue("SearchHiLight") !== false) { //If the value doesn't exist define as true
  GM_setValue("SearchHiLight", true); //Defines the variable as true
} //Finishes the if condition

if (GM_getValue("CurrenciesConverter") !== true && GM_getValue("CurrenciesConverter") !== false) { //If the value doesn't exist define as true
  GM_setValue("CurrenciesConverter", true); //Defines the variable as true
} //Finishes the if condition

if (GM_getValue("UnitsConverter") !== true && GM_getValue("UnitsConverter") !== false) { //If the value doesn't exist define as true
  GM_setValue("UnitsConverter", true); //Defines the variable as true
} //Finishes the if condition

function SearchHiLight() //Function to enable or disable the SearchHiLight and the Currency/Unit converters
{ //Starts the function SearchHiLight
  if (GM_getValue("SearchHiLight") === true) { //If the last config was true, set as false
    GM_setValue("SearchHiLight", false); //Defines the variable as false
    GM_setValue("CurrenciesConverter", false); //Defines the variable as false
    GM_setValue("UnitsConverter", false); //Defines the variable as false
  } //Finishes the if condition
  else { //If the last config was false, set as true
    GM_setValue("SearchHiLight", true); //Defines the variable as true

    if (confirm('If you want to enable the Currency Converter press OK.')) //Show the confimation alert box text
    { //Starts the if condition
      GM_setValue("CurrenciesConverter", true); //Defines the variable as true
    } //Finishes the if condition
    else //If the user pressed cancel
    { //Starts the else condition
      GM_setValue("CurrenciesConverter", false); //Defines the variable as false
    } //Finishes the else condition

    if (confirm('If you want to enable the Units Converter press OK.')) //Show the confimation alert box text
    { //Starts the if condition
      GM_setValue("UnitsConverter", true); //Defines the variable as true
    } //Finishes the if condition
    else //If the user pressed cancel
    { //Starts the else condition
      GM_setValue("UnitsConverter", false); //Defines the variable as false
    } //Finishes the else condition

    location.reload(); //Reloads the page
  } //Finishes the else condition
} //Finishes the function SearchHiLight

if (GM_getValue("SearchHiLight") === true) //If the SearchHiLight is enabled
{ //Starts the if condition

  var $ = window.jQuery; //Defines That The Symbol $ Is A jQuery
  var SelectedTextIsLink; //Creates a new global variable
  var Links = new RegExp(/\.org|\.ly|\.net|\.co|\.tv|\.me|\.biz|\.club|\.site|\.br|\.gov|\.io|\.jp|\.edu|\.au|\.in|\.it|\.ca|\.mx|\.fr|\.tw|\.il|\.uk|\.zoom\.us/i); //Creates a global variable to check if a link is matched
  var FinalCurrency, SelectedText, SelectedTextSearch = ''; //Make these variables global

  $(function() { //Starts the function
    var menu = $("#highlight_menu", $("#highlight_menu_div")[0].shadowRoot); //Creates a variable to hold the menu element
    $(document.body).on('mouseup', function() { //When the user releases the mouse click after selecting something
      SelectedText = window.getSelection().toString(); //Creates a variable to store the selected text
      SelectedTextSearch = window.getSelection().toString().replaceAll('&', '%26'); //Creates a variable to store the selected text to be opened on google

      if (GM_getValue("CurrenciesConverter") === true) { //If the Currencies Converter option is activated
        document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Currency text
        var Currencies = new RegExp(/^[ \t\xA0]*(?=.*?(\d+(?:.\d+)?))(?=(?:\1[ \t\xA0]*)?(Dólares|dolares|dólares|dollars|AUD|BGN|BRL|BCH|BTC|BYN|CAD|CHF|CNY|CZK|DKK|EUR|EGP|ETH|GBP|GEL|HKD|HRK|HUF|IDR|ILS|INR|JPY|LTC|KRW|MXN|MYR|NOK|NZD|PHP|PLN|RON|RM|RUB|SEK|SGD|THB|TRY|USD|UAH|ZAR|KZT|YTL|\$|R\$|HK\$|US\$|\$US|¥|€|Rp|kn|Kč|kr|zł|£|฿|₩))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i); //Adds an regex expression to the Currencies variable

        if (SelectedText.match(Currencies) !== null) //If the selected text is a currency
        { //Starts the if condition

          if (GM_getValue("YourLocalCurrency") === undefined) { //If the value is undefined
            var UserInput = prompt('This is the first time that you selected a currency.\nThis is the first and last time this popup will appear, so please write your local currency so that the script will always use your local currency to make exchange rate conversions.\n*Example of what you should write: BRL\nCAD\nUSD\netc...\n*Press OK'); //Gets the user input
            GM_setValue("YourLocalCurrency", UserInput); //Defines the variable as the UserInput
          } //Finishes the if condition

          (async () => { //Creates a function to get the final value
            var CurrencySymbol = SelectedText.match(Currencies)[2]; //Get the actual currency symbol supposing "it's correct"
            var CurrencySymbols = new RegExp(/\$|R\$|HK\$|US\$|\$US|¥|€|Rp|kn|Kč|kr|zł|£|฿|₩/i); //Create a variable to check for the selected currency symbols
            if (SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null) //If the selected currency contains a symbol
            { //Starts the if condition

              switch (SelectedText.match(CurrencySymbols)[0].toLowerCase()) { //If the selected currency constains a symbol
                case '$': //Get the actual selected currency symbol
                case 'us$': //Get the actual selected currency symbol
                case '$us': //Get the actual selected currency symbol
                  CurrencySymbol = 'USD'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'r$': //Get the actual selected currency symbol
                  CurrencySymbol = 'BRL'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'hk$': //Get the actual selected currency symbol
                  CurrencySymbol = 'HKD'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case "¥": //Get the actual selected currency symbol
                  CurrencySymbol = 'JPY'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case '€': //Get the actual selected currency symbol
                  CurrencySymbol = 'EUR'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'rp': //Get the actual selected currency symbol
                  CurrencySymbol = 'IDR'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'kn': //Get the actual selected currency symbol
                  CurrencySymbol = 'HRK'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'kč': //Get the actual selected currency symbol
                  CurrencySymbol = 'CZK'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'kr': //Get the actual selected currency symbol
                  CurrencySymbol = 'DKK'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case 'zł': //Get the actual selected currency symbol
                  CurrencySymbol = 'PLN'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case '£': //Get the actual selected currency symbol
                  CurrencySymbol = 'GBP'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case '฿': //Get the actual selected currency symbol
                  CurrencySymbol = 'THB'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
                case '₩': //Get the actual selected currency symbol
                  CurrencySymbol = 'KRW'; //"Convert" the symbol to the Currency Letters
                  break; //Stop trying to get the correct Currency Letters
              } //Finishes the switch condition
            } //Finishes the if condition

            const url = `https://api.allorigins.win/get?url=${encodeURIComponent('https://www.google.com/search?q=' + SelectedText.match(Currencies)[1] + ' ' + CurrencySymbol + ' in ' + GM_getValue("YourLocalCurrency"))}`;

            const responsehtml = GM_info.scriptHandler === 'FireMonkey' ? (await GM.fetch(url, {responseType: 'json'})).json :
                await (await fetch(url)).json(); //Fetch

            const newDocument = new DOMParser().parseFromString(responsehtml.contents, 'text/html'); //Parses the fetch response
            FinalCurrency = parseFloat(newDocument.querySelector("div.BNeawe.iBp4i.AP7Wnd").innerText.split(' ')[0].replaceAll(',', '')); //Gets the final amount of money

            if (SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null) //If the selected currency contains a symbol
            { //Starts the if condition

              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = CurrencySymbol + ' 🠂 ' + Intl.NumberFormat(navigator.language, {
                style: 'currency',
                currency: GM_getValue("YourLocalCurrency")
              }).format(FinalCurrency) + ' | '; //Show the FinalCurrency on the menu
            } //Finishes the if condition
            else //If the selected currency contains no symbol
            { //Starts the else condition
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = Intl.NumberFormat(navigator.language, {
                style: 'currency',
                currency: GM_getValue("YourLocalCurrency")
              }).format(FinalCurrency) + ' | '; //Show the FinalCurrency on the menu
            } //Finishes the else condition

            var text = document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText; //Save the actual currency text

            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmousemove = function() { //When the mouse is hovering the currency
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Copy | "; //Change the element text to copy
            }; //Finishes the onmousemove advent listener

            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the button
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = text; //Return the previous text
            }; //Finishes the onmouseout advent listener

            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the user clicks on the currency
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Done | "; //Change the element text to copy
              navigator.clipboard.writeText(Intl.NumberFormat(navigator.language, {
                style: 'currency',
                currency: GM_getValue("YourLocalCurrency")
              }).format(FinalCurrency)); //Copy the Final Currency
            }; //Finishes the onclick advent listener

          })(); //Finishes the async function
        } //Finishes the if condition
      } //Finishes the if condition
      //___________________________________________________________________________________________________________________________________________________________________________

      if (GM_getValue("UnitsConverter") === true) { //If the Units Converter option is activated
        document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Units text
        var Units = new RegExp(/^[ \t\xA0]*(-?\d+(?:[., ]\d+)?)[ \t\xA0]*(inch|inches|in|cms?|centimeters?|meters?|ft|kg|lbs?|pounds?|kilograms?|ounces?|g|ozs?|fl oz|fl oz (us)|fluid ounces?|kphs?|km\/h|kilometers per hours?|mphs?|meters per hours?|°[CF]|km\/hs?|ml|milliliters?|l|liters?|litres?|gal|gallons?|yards?|yd|Millimeter|millimetre|kilometers?|mi|mm|miles?|km|ft|fl|feets?|mts?|grams?|kilowatts?|kws?|brake horsepower|mechanical horsepower|hps?|bhps?|miles per gallons?|mpgs?|liters per 100 kilometers?|l\/100km|liquid quarts?|lqs?|foot-?pounds?|ft-?lbs?|lb fts?|newton-?meters?|nm)[ \t\xA0]*(?:\(\w+\)[ \t\xA0]*)?$/i); //Adds an regex expression to the Units variable

        if (SelectedText.match(Units) !== null) //If the selected text is an unit
        { //Starts the if condition

          var SelectedUnitValue = SelectedText.match(Units)[1].replaceAll(',', '.'); //Get the selected unit value and store the value to a variable

          switch (SelectedText.match(Units)[2].toLowerCase()) { //Set all letters to Lower Case and convert the selected unit to another unit the same way the Opera Browser does
            case 'inch': //Get the actual selected unit type
            case 'inches': //Get the actual selected unit type
            case 'in': //Get the actual selected unit type
              var NewUnit = 'cm'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 2.54).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'centimeter': //Get the actual selected unit type
            case 'centimeters': //Get the actual selected unit type
            case 'cm': //Get the actual selected unit type
            case 'cms': //Get the actual selected unit type
              NewUnit = 'in'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 2.54).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'mt': //Get the actual selected unit type
            case 'mts': //Get the actual selected unit type
            case 'meters': //Get the actual selected unit type
            case 'meter': //Get the actual selected unit type
              NewUnit = 'ft'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 3.281).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'kg': //Get the actual selected unit type
            case 'kilograms': //Get the actual selected unit type
            case 'kilogram': //Get the actual selected unit type
              NewUnit = 'lb'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 2.205).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'pound': //Get the actual selected unit type
            case 'pounds': //Get the actual selected unit type
            case 'lb': //Get the actual selected unit type
            case 'lbs': //Get the actual selected unit type
              NewUnit = 'kg'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 2.205).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'ounce': //Get the actual selected unit type
            case 'ounces': //Get the actual selected unit type
            case 'oz': //Get the actual selected unit type
            case 'ozs': //Get the actual selected unit type
              NewUnit = 'g'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 28.35).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'g': //Get the actual selected unit type
            case 'gram': //Get the actual selected unit type
            case 'grams': //Get the actual selected unit type
              NewUnit = 'oz'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 28.35).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'kilometer': //Get the actual selected unit type
            case 'kilometers': //Get the actual selected unit type
              NewUnit = 'mi'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 1.609).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'kph': //Get the actual selected unit type
            case 'kphs': //Get the actual selected unit type
            case 'km/h': //Get the actual selected unit type
            case 'km/hs': //Get the actual selected unit type
            case 'kilometers per hour': //Get the actual selected unit type
            case 'kilometers per hours': //Get the actual selected unit type
              NewUnit = 'mph'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 1000).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'mph': //Get the actual selected unit type
            case 'mphs': //Get the actual selected unit type
            case 'meters per hour': //Get the actual selected unit type
            case 'meters per hours': //Get the actual selected unit type
              NewUnit = 'km/h'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 1.000).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'mi': //Get the actual selected unit type
            case 'mile': //Get the actual selected unit type
            case 'miles': //Get the actual selected unit type
              NewUnit = 'km'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 1.609).toFixed(2); //Gets the converted unit result
              break; //Stop
            case '°c': //Get the actual selected unit type
              NewUnit = '°F'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat((SelectedUnitValue * 9 / 5) + 32).toFixed(2); //Gets the converted unit result
              break; //Stop
            case '°f': //Get the actual selected unit type
              NewUnit = '°C'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat((SelectedUnitValue - 32) * 5 / 9).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'ml': //Get the actual selected unit type
            case 'milliliter': //Get the actual selected unit type
            case 'milliliters': //Get the actual selected unit type
              NewUnit = 'fl oz (US)'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 29.574).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'fl oz (US)': //Get the actual selected unit type
            case 'fl oz': //Get the actual selected unit type
            case 'fl': //Get the actual selected unit type
            case 'fluid ounce': //Get the actual selected unit type
            case 'fluid ounces': //Get the actual selected unit type
              NewUnit = 'ml'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 29.574).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'l': //Get the actual selected unit type
            case 'litre': //Get the actual selected unit type
            case 'liter': //Get the actual selected unit type
            case 'litres': //Get the actual selected unit type
            case 'liters': //Get the actual selected unit type
              NewUnit = 'gal (US)'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 3.785).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'gal': //Get the actual selected unit type
            case 'gallon': //Get the actual selected unit type
            case 'gallons': //Get the actual selected unit type
              NewUnit = 'lt'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 3.785).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'yard': //Get the actual selected unit type
            case 'yards': //Get the actual selected unit type
            case 'yd': //Get the actual selected unit type
              NewUnit = 'm'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 1.094).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'mm': //Get the actual selected unit type
            case 'millimetre': //Get the actual selected unit type
            case 'Millimeters': //Get the actual selected unit type
              NewUnit = 'in'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 25.4).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'ft': //Get the actual selected unit type
            case 'feet': //Get the actual selected unit type
            case 'feets': //Get the actual selected unit type
              NewUnit = 'mt'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 0.3048).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'kw': //Get the actual selected unit type
            case 'kws': //Get the actual selected unit type
            case 'kilowatt': //Get the actual selected unit type
            case 'kilowatts': //Get the actual selected unit type
              NewUnit = 'mhp'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 1.341).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'mhp': //Get the actual selected unit type
            case 'mhps': //Get the actual selected unit type
            case 'hp': //Get the actual selected unit type
            case 'hps': //Get the actual selected unit type
            case 'brake horsepower': //Get the actual selected unit type
            case 'mechanical horsepower': //Get the actual selected unit type
              NewUnit = 'kw'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 1.341).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'mpg': //Get the actual selected unit type
            case 'mpgs': //Get the actual selected unit type
            case 'miles per gallon': //Get the actual selected unit type
            case 'miles per gallons': //Get the actual selected unit type
              NewUnit = 'l/100km'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(235.215 / SelectedUnitValue).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'l/100km': //Get the actual selected unit type
            case 'liters per 100 kilometer': //Get the actual selected unit type
            case 'liters per 100 kilometers': //Get the actual selected unit type
              NewUnit = 'US mpg'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(235.215 / SelectedUnitValue).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'lq': //Get the actual selected unit type
            case 'lqs': //Get the actual selected unit type
            case 'liquid quart': //Get the actual selected unit type
            case 'liquid quarts': //Get the actual selected unit type
              NewUnit = 'l'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 1.057).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'liter': //Get the actual selected unit type
            case 'liters': //Get the actual selected unit type
              NewUnit = 'lqs'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 1.057).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'foot-pound': //Get the actual selected unit type
            case 'foot-pounds': //Get the actual selected unit type
            case 'foot pound': //Get the actual selected unit type
            case 'foot pounds': //Get the actual selected unit type
            case 'ft-lbs': //Get the actual selected unit type
            case 'ft-lb': //Get the actual selected unit type
            case 'ft lbs': //Get the actual selected unit type
            case 'ft lb': //Get the actual selected unit type
            case 'lb ft': //Get the actual selected unit type
            case 'lb fts': //Get the actual selected unit type
              NewUnit = 'Nm'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue * 1.3558179483).toFixed(2); //Gets the converted unit result
              break; //Stop
            case 'newton-meter': //Get the actual selected unit type
            case 'newton-meters': //Get the actual selected unit type
            case 'newton meter': //Get the actual selected unit type
            case 'newton meters': //Get the actual selected unit type
            case 'nm': //Get the actual selected unit type
              NewUnit = 'ft lb'; //"Convert" the current unit to another unit
              ConvertedUnit = parseFloat(SelectedUnitValue / 1.3558179483).toFixed(2); //Gets the converted unit result
              break; //Stop
          } //Finishes the switch condition

          setTimeout(function() { //Starts the settimeout function
            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ConvertedUnit + ' ' + NewUnit + ' | '; //Display the converted unit results

            var text = document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText; //Save the actual converted unit value

            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmousemove = function() { //When the mouse is hovering the converted unit value
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Copy | "; //Change the element text to copy
            }; //Finishes the onmousemove advent listener

            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the button
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = text; //Return the previous text
            }; //Finishes the onmouseout advent listener

            document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the user clicks on the converted unit value
              document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = "Done | "; //Change the element text to copy
              navigator.clipboard.writeText(ConvertedUnit); //Copy the Final converted unit value
            }; //Finishes the onclick advent listener
          }, 0); //Finishes the settimeout
        } //Finishes the if condition
      } //Finishes the if condition
      //___________________________________________________________________________________________________________________________________________________________________________
      if (document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").innerText === 'Open') //If the Search butotn text is 'Open'
      { //Starts the if condition
        document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu > ul").style.paddingLeft = '7px'; //Change the ul menu element padding left css style
        document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").innerText = 'Search'; //The next time that the menu is shown display the button text as Search again
        SelectedTextIsLink = false; //Add the value false to the variable to make common words searchable again
      } //Finishes the if condition

      if (SelectedText.match(Links) !== null) //If the selected text is a link
      { //Starts the if condition
        SelectedTextIsLink = true; //Add the value true to the variable
        document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu > ul").style.paddingLeft = '7px'; //Change the ul menu element padding left css style
        document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").innerText = 'Open'; //Change the button text to Open
      } //Finishes the if condition
      //___________________________________________________________________________________________________________________________________________________________________________
      if (document.getSelection().toString().trim() !== '') { //If the user selected something
        var p = document.getSelection().getRangeAt(0).getBoundingClientRect(); //Create a new variable to get the positions later
        menu.css({ //Set the menu css
          left: (p.left + (p.width / 2)) - (menu.width() / 2), //Make the menu show on the correct left position
          top: (p.top - menu.height() - 10), //Make the menu show on the correct top position
          display: '' //Show the menu on the page
        }).animate({ //Creates the animation
          opacity: 1 //Set the animation opacity
        }, 0); //Add an animation to the menu

        menu.addClass('highlight_menu_animate'); //Add the class to animate the menu
        //$('head').append('<style>.highlight_menu_animate .popuptext::after { content: ""; top: 100%; left: 50%; margin-left: -10px; position: absolute; border-style: solid; border-color: #292929 transparent transparent transparent; }</style>'); //Create a class to animate the menu
        return; //Keep displaying the menu box
      } //Finishes the if condition
      menu.animate({ //Creates the animation
        opacity: 0 //Set the animation opacity
      }); //Hide the menu If the user clicked on any of the options

      setTimeout(function() { //Hide the menu after some time
        menu.css({ //Set the menu css
          display: 'none' //Hide the menu on the page
        }); //Hide the menu If the user clicked on any of the options
      }, 300); //Finishes the setTimeout function

    }); //Finishes the mouseup advent listener
  }); //Finishes the function
  //___________________________________________________________________________________________________________________________________________________________________________
  var HtmlMenu = document.createElement('div'); //Creates a variable
  HtmlMenu.setAttribute("id", "highlight_menu_div"); //Set the div id to the HtmlMenu variable

  HtmlMenu.attachShadow({
    mode: 'open'
  }).innerHTML = '<style>.highlight_menu_animate .popuptext::after { content: ""; top: 100%; left: 50%; margin-left: -10px; border-top-width: 10px; border-right-width: 10px; border-left-width: 10px; position: absolute; border-style: solid; border-color: #292929 transparent transparent transparent; }</style><div id="highlight_menu" style="display:none; color: #fff; position: fixed; background-color: #292929; font-size: 13.4px; font-family: monospace; z-index: 9999;"> <ul style="margin-block-end: 10px; padding-left: 7px; padding-right: 7px; margin-top:10px;"><li class="popuptext" id="ShowCurrencyORUnits" style="cursor: pointer; display: inline;">' + FinalCurrency + '</li><li class="popuptext" id="SearchBTN" style="cursor: pointer; display: inline;">Search</li><li class="popuptext" id="CopyBTN" style="cursor: pointer; display: inline;"> | Copy</li></ul></div>'; //Set the HtmlMenu div html

  if (document.body.textContent !== '' || document.body.innerText !== '') //If the body has any text
  { //Starts the if condition
    document.body.appendChild(HtmlMenu); //Append the HtmlMenu div to the page
  } //Finishes the if condition

  document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#SearchBTN").onmousedown = function() { //When the user clicks on the Search button
    var LinkfyOrSearch = 'https://www.google.com/search?q='; //Creates a variable to open google
    if (SelectedTextIsLink === true) //If the selected text is a link
    { //Starts the if condition
      LinkfyOrSearch = 'https://'; //Make the non http and non https links able to be opened
    } //Finishes the if condition
    if (SelectedText.match(/http:|https:/) !== null) //If the selected text is a link that already has http or https
    { //Starts the if condition
      LinkfyOrSearch = ''; //Remove the https:// that was previsouly added to this variable
    } //Finishes the if condition

    window.open(LinkfyOrSearch + SelectedTextSearch); //Open google and search for the selected word(s)
    window.getSelection().removeAllRanges(); //UnSelect the selected text after the search button is clicked so that if the user clicks on the past selected text the menu won't show up again.
    document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu").style.display = 'none'; //Hide the menu
  }; //Finishes the onmousedown advent listener

  document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#CopyBTN").onmousedown = function() { //When the user clicks on the copy button
    navigator.clipboard.writeText(SelectedText); //Copy the selected word(s)
    document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#CopyBTN").innerText = ' | Done'; //Change the button text to Done
    setTimeout(function() { //Starts the setTimeout function
      document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#CopyBTN").innerText = ' | Copy'; //The next time that the menu is shown display the button text as Copy again, instead of Done
    }, 400); //Finishes the setTimeout function
  }; //Finishes the onmousedown advent listener
  //___________________________________________________________________________________________________________________________________________________________________________
  setTimeout(function() { //Starts the setTimeout function
    var AllIframes = document.querySelectorAll("iframe"); //Get all iframes on the page
    for (var i = AllIframes.length; i--;) { //Starts the for condition
      if (AllIframes[i].allow.match('clipboard-write;') === null && AllIframes[i].src.match(Links) !== null && AllIframes[i].src.match(/youtube|dailymotion|vimeo|streamtape|mcloud|vidstream|mp4upload|googlevideo|kaltura|crunchyroll|animesup|google.com\/recaptcha\/|blank.html|\.mp4/) === null && location.href.match(/animeshouse.net|nowanimes.com\/play/) === null) //If the iframe doesn't have the clipboard-write attribute yet and the iframed source attribute has a link. And if the iframe isn't an YT/dailymotion or vimeo video.
      { //Starts the if condition
        AllIframes[i].allow = AllIframes[i].allow + 'clipboard-write;'; //Add the permission to copy the iframe text
        AllIframes[i].src = AllIframes[i].src; //Reload the iframe to make the iframe have the new permission
      } //Finishes the if condition
    } //Finishes the for condition
  }, 4000); //Finishes the setTimeout function

  window.addEventListener('scroll', async function() { //When the page is scrolled
    document.querySelector("#highlight_menu_div").shadowRoot.querySelector("#highlight_menu").style.display = 'none'; //Hide the menu
    if (LeftClicked === false && SelectedText !== '') { //If the Left Click isn't being holded, and if something is currently selected
      window.getSelection().removeAllRanges(); //UnSelect the selected text when scrolling the page down so that if the user clicks on the past selected text the menu won't show up again.
    } //Finishes the if condition
  }); //Finishes the onscroll advent listener
} //Finishes the if condition

Here is another script that does not work with FM

I will check it out .

Ghost-BD commented 2 years ago

Works properly with your changes. Thanks.

erosman commented 2 years ago

@Ghost-BD FireMonkey v2.53 should work with original Opera Browser Rocker+Mouse without the need to change for FM. Try it and let me know.

SMylk commented 2 years ago

How do i make https://sleazyfork.org/en/scripts/109-handy-image work with firemonkey? In tampermonkey it works without any issue.

erosman commented 2 years ago

How do i make https://sleazyfork.org/en/scripts/109-handy-image work with firemonkey?

Which version of FireMonkey are you using? There is a workaround for XMLHttpRequest in v2.53 which applies to this script. What happens?

SMylk commented 2 years ago

Which version of FireMonkey are you using? There is a workaround for XMLHttpRequest in v2.53 which applies to this script. What happens?

Version 2.53 Opening image on supported page and nothing happens (for example https://imagetwist.com/tg41qn3lphx4/1.png) it's supposed to strip advertisement, if I try to run script by hand i get a "Failed to insert return not in function" error.

erosman commented 2 years ago

if I try to run script by hand i get a "Failed to insert return not in function" error.

It doesn't even get injected because the userscript has parse error. :man_shrugging:

This can be checked in Browser Console (Ctrl+Shift+J)

SyntaxError: return not in function                              line 878

Tampermonkey, Violentmonkey & Greasemonkey wrap the userscripts in an anonymous function in order to limit its exposure to the page JavaScript.

As a result, such JavaScript parse errors do not cause a problem but that created a situation where developers write JavaScript with errors The code has many invalid return e.g. lines: 878, 883, 890, 901, 912

As a workaround, you can add the following to the start and end of the userscript:

At the start

(() => {

At the end

})();

Note

This issue is similar to https://github.com/erosman/support/issues/429#issuecomment-1063692016 as I have posted to the "General URL Cleaner Revived" topic.

SMylk commented 2 years ago

At the start

(() => {

At the end

})();

Hey thanks, this indeed makes it work. And also somehow makes it run on imdb too, i only see a pic when opening imdb, but if I disable handy userscript then imdb is back to normal, but firemonkey is not even shows it running. https://www.imdb.com/title/tt11138512/

erosman commented 2 years ago

And also somehow makes it run on imdb too, i only see a pic when opening imdb, but if I disable handy userscript then imdb is back to normal, but firemonkey is not even shows it running.

That is due to the mix of @match & regular expression. Regular Expression support is experimental & limited in FireMonkey.

Quick temporary fix Add the following to the User Metadata

@disable-exclude    /^https://(.*\.)?xhamster(\d{1,2})?\.(com|desi)/photos/gallery/.*/\d{1,3}$/
erosman commented 2 years ago

:pushpin: Tampermonkey is deprecating @include

Convert deprecated @include with regular expression to @match in userscript

eslint: userscripts/better-use-match - Using @include is potentially unsafe and may be obsolete in Manifest v3 in early 2023. Please switch to @match.

ref: Tampermonkey lint.js & /_locales/en/messages.json

SMylk commented 2 years ago

@disable-exclude /^https://(.*\.)?xhamster(\d{1,2})?\.(com|desi)/photos/gallery/.*/\d{1,3}$/ Indeed, this makes it work, many thanks!

Is there any way to make these changes stick, even after a script update?

erosman commented 2 years ago

Is there any way to make these changes stick, even after a script update?

User Metadata stays after script update. There is no function in FM to wrap script in an anonymous function at the moment.

TBH, it is an easy fix for userscript developers to write JavaScript without parse error. I use a template like the following for all my userscripts.

// ==UserScript==
// @name
// @match
// @author
// @version
// ==/UserScript==

(async () => { // anonymous function wrapper, for error checking & limiting scope, async FF52+
'use strict';

// end of anonymous function
})();