RichardW
RichardW

Reputation: 259

Chrome Extension: How to Disable Page Visibility API

I am writing a Chrome extension that needs to prevent webpages from triggering the document visibilitychange event. At the very least I need to be able to overwrite the document.visibilityState (even though it is a read-only property). If not possible, since this extension is for my purposes only and will not be on the Chrome extension store, is there a way I can config my Chrome Browser to achieve what I want? I only need to use this extension while Chrome "Developer Mode" is on, no other time.

I hope someone can think of a creative way to achieve this. Thank you.

Please note! There was a solution in an answer 4 years ago that no longer takes effect in newer versions of Chrome: Spoof or disable the Page Visibility API

Test it out yourself:

// This codes worked 4 years ago but not anymore
var c='(function(){var a=Node.prototype.addEventListener;Node.prototype.addEventListener=function(e){if(e=="visibilitychange"||e=="webkitvisibilitychange"){}else a.apply(this,arguments)}})()'
, E=document.documentElement;
E.setAttribute('onreset', c);
E.dispatchEvent(new CustomEvent('reset'));
E.removeAttribute('onreset');

// THIS WILL STILL LOG THE STATES EVEN WITH THE ABOVE CODE RUNNING
document.addEventListener("visibilitychange", function() {
    console.log( document.visibilityState );
});

If its not possible in Chrome, is there Firefox/Safari/Opera Browser code that can achieve this?

Upvotes: 10

Views: 8212

Answers (3)

nikicat
nikicat

Reputation: 388

This userscript works well in my case. It's code is relatively compact.

unsafeWindow.onblur = null;
unsafeWindow.blurred = false;
 
unsafeWindow.document.hasFocus = function () {return true;};
unsafeWindow.window.onFocus = function () {return true;};
 
Object.defineProperty(document, "hidden", { value : false});
Object.defineProperty(document, "mozHidden", { value : false});
Object.defineProperty(document, "msHidden", { value : false});
Object.defineProperty(document, "webkitHidden", { value : false});
Object.defineProperty(document, 'visibilityState', { get: function () { return "visible"; } });
 
unsafeWindow.document.onvisibilitychange = undefined;
 
for (event_name of ["visibilitychange",
                    "webkitvisibilitychange",
                    "blur", // may cause issues on some websites
                    "mozvisibilitychange",
                    "msvisibilitychange"]) {
  window.addEventListener(event_name, function(event) {
        event.stopImmediatePropagation();
    }, true);
}

Note that unsafeWindow is only available inside a userscript host extension (like Tampermonkey) and is an unwrapped window object as seen by a page.

Upvotes: 1

Mwalek
Mwalek

Reputation: 39

I found a great solution on StackExchange:

Object.defineProperty(document, 'visibilityState', {value: 'visible', writable: true});
Object.defineProperty(document, 'hidden', {value: false, writable: true});
document.dispatchEvent(new Event("visibilitychange"));

In case anyone has trouble getting the above solution or any of the other solutions on this page to work inside their browser extension (like I did), the solution is to add the script that spoofs the visibility API directly to the DOM.

In manifest.json I added the following:

"content_scripts": [
    {
      "matches": ["*://*/*"],
      "js": ["disable.js"]
    }
 ]

And then I used disable.js to add a script that actually manipulates the page.

let s = document.createElement("script");
s.setAttribute("id", "yourScriptID");
s.textContent = `
/**
 * Monitors changes to the page visibility.
 */
document.addEventListener('visibilitychange', () => {
  /**
   * Uncomment this line to debug whether the
   * Page Visibility API is being spoofed.
   * console.log('Document.hidden = "${document.hidden}".');
   **/
    Object.defineProperty(document, 'visibilityState', {
        value: 'visible',
        writable: true,
    });
    Object.defineProperty(document, 'hidden', { value: false, writable: true });
  });`;
(document.head || document.documentElement).appendChild(s);

The reason for doing things this way is that "content scripts live in an isolated world."

What this means is that they have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page.

You'll find the following discussion very helpful when it comes to understanding how content scripts interact with pages and how to safely/properly inject scripts via your extension:

Access variables and functions defined in page context using a content script

HTH, Mwale

Upvotes: 2

Navin
Navin

Reputation: 4107

Here's my solution:

for (event_name of ["visibilitychange", "webkitvisibilitychange", "blur"]) {
  window.addEventListener(event_name, function(event) {
        event.stopImmediatePropagation();
    }, true);
}

I added the blur event because the video I wanted to skip (everfi.net) used it to detect when I switched windows. Blocking that event along with visibilitychange and webkitvisibilitychange did the trick :)

I also modified the extension's manifest so that it works inside iframes.

Full code (chrome extension): https://github.com/NavinF/dont

Confirmed working with the following dog tags:

Google Chrome   63.0.3239.132 (Official Build) (64-bit)
Revision    2e6edcfee630baa3775f37cb11796b1603a64360-refs/branch-heads/3239@{#709}
OS  Mac OS X
JavaScript  V8 6.3.292.49
Command Line    /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --flag-switches-begin --flag-switches-end

Upvotes: 24

Related Questions