Skip to content

Instantly share code, notes, and snippets.

@hughpearse
Last active July 31, 2023 13:00
Show Gist options
  • Save hughpearse/9454406ad43116dfaef8c43e7eac84f4 to your computer and use it in GitHub Desktop.
Save hughpearse/9454406ad43116dfaef8c43e7eac84f4 to your computer and use it in GitHub Desktop.
Android Bookmarklet - Reader View (Simplified Page)

Android Bookmarklet - Reader View (Simplified Page)

In chrome on android the "Simplified Page" feature does not have a button. If for some reason the browser decides not to display the layover toast message to access this feature, then the user is unable to enter "simplified view". The solution to this problem has been discussed before on Stack Overflow here, where they suggest opening a url to access a local resource on the browser cache. The local resource URL is formatted as follows:

chrome-distiller://<UUID>_<HASH>/?url=<URL>

It is not permitted to perform a javascript redirect to a local resource in the browser, however it is possible to display the URL as text.

javascript:function customModulo(r,o){const a=Math.floor(r/o);const t=r-o*a;return t}var sha256=function r(o){function a(r,o){return r>>>o|r<<32-o}var t=Math.pow;var n=t(2,32);var v="length";var f,e;var c="";var i=[];var u=o[v]*8;var l=r.h=r.h||[];var s=r.k=r.k||[];var h=s[v];var d={};for(var w=2;h<64;w++){if(!d[w]){for(f=0;f<313;f+=w){d[f]=w}l[h]=t(w,.5)*n|0;s[h++]=t(w,1/3)*n|0}}o+="\x80";while(customModulo(o[v],64)-56)o+="\0";for(f=0;f<o[v];f++){e=o.charCodeAt(f);if(e>>8)return;i[f>>2]|=e<<customModulo(3-f,4)*8}i[i[v]]=u/n|0;i[i[v]]=u;for(e=0;e<i[v];){var M=i.slice(e,e+=16);var g=l;l=l.slice(0,8);for(f=0;f<64;f++){var m=f+e;var k=M[f-15],S=M[f-2];var p=l[0],A=l[4];if(f<16){M[f]=M[f]}else{M[f]=M[f-16]+(a(k,7)^a(k,18)^k>>>3)+M[f-7]+(a(S,17)^a(S,19)^S>>>10)|0}var C=l[7]+(a(A,6)^a(A,11)^a(A,25))+(A&l[5]^~A&l[6])+s[f]+M[f];var b=(a(p,2)^a(p,13)^a(p,22))+(p&l[1]^p&l[2]^l[1]&l[2]);l=[C+b|0].concat(l);l[4]=l[4]+C|0}for(f=0;f<8;f++){l[f]=l[f]+g[f]|0}}for(f=0;f<8;f++){for(e=3;e+1;e--){var j=l[f]>>e*8&255;c+=function(){if(j<16){return"0"+j.toString(16)}else{return j.toString(16)}}()}}return c};var readerUrl="chrome-distiller://00000000-0000-0000-0000-000000000000_"+sha256(window.location.href)+decodeURIComponent("/%3Furl=")+window.location.href;window.location=readerUrl;

It is also tricky to open a bookmarklet in Chrome on Android. The process is described here.

To use:

Save this bookmark to Chrome in Android, then open a web page you want to read, then search in the address bar for the bookmarklet, and then open it.

@antoniocosta
Copy link

antoniocosta commented Jul 31, 2023

It displays readerUrl var on a blank page, but does not open it unfortunately.

@kbauer
Copy link

kbauer commented Jul 31, 2023

Changing location.href is disallowed by security policies. I updated the script to use the clipboard instead.

Minified version for bookmarklet use

javascript:/* Chrome Reader View.js - 10:25:07 2023-07-31 */(function(){ main();function main(){document.head.innerHTML="";document.body.innerHTML="<input id='reader-url-input' style='width: 100%25'/>"+"<button id='reader-url-button' style='width: 100%25'>Copy to Clipboard</button>";let inp=document.getElementById("reader-url-input");let but=document.getElementById("reader-url-button");inp.value=readerViewUrl();but.addEventListener("click",copyReaderUrl);copyReaderUrl();function copyReaderUrl(){inp.select();inp.setSelectionRange(0,99999);navigator.clipboard.writeText(inp.value);}location.href=readerViewUrl();}function readerViewUrl(){return"chrome-distiller:/"+"/00000000-0000-0000-0000-000000000000_"+sha256(window.location.href)+"/?url="+window.location.href;}function customModulo(r,o){let a=Math.floor(r/o);let t=r-o*a;return t}function sha256(o){let r=sha256;function a(r,o){return r>>>o|r<<32-o}var t=Math.pow;var n=t(2,32);var v="length";var f,e;var c="";var i=[];var u=o[v]*8;var l=r.h=r.h||[];var s=r.k=r.k||[];var h=s[v];var d={};for(var w=2;h<64;w++){if(!d[w]){for(f=0;f<313;f+=w){d[f]=w}l[h]=t(w,.5)*n|0;s[h++]=t(w,1/3)*n|0}}o+="\x80";while(customModulo(o[v],64)-56)o+="\0";for(f=0;f<o[v];f++){e=o.charCodeAt(f);if(e>>8)return;i[f>>2]|=e<<customModulo(3-f,4)*8}i[i[v]]=u/n|0;i[i[v]]=u;for(e=0;e<i[v];){var M=i.slice(e,e+=16);var g=l;l=l.slice(0,8);for(f=0;f<64;f++){var m=f+e;var k=M[f-15],S=M[f-2];var p=l[0],A=l[4];if(f<16){M[f]=M[f]}else{M[f]=M[f-16]+(a(k,7)^ a(k,18)^ k>>>3)+M[f-7]+(a(S,17)^ a(S,19)^ S>>>10)|0}var C=l[7]+(a(A,6)^ a(A,11)^ a(A,25))+(A&l[5]^ ~A&l[6])+s[f]+M[f];var b=(a(p,2)^ a(p,13)^ a(p,22))+(p&l[1]^ p&l[2]^ l[1]&l[2]);l=[C+b|0].concat(l);l[4]=l[4]+C|0}for(f=0;f<8;f++){l[f]=l[f]+g[f]|0}}for(f=0;f<8;f++){for(e=3;e+1;e--){var j=l[f]>>e*8&255;c+=function(){if(j<16){return"0"+j.toString(16)}else{return j.toString(16)}}()}}return c};})();undefined;

Formatted source code

// Script overall based on [hp01]. Updated 2023/07/31 by kbauer.
//
// Usage
//   1. Add minified version (one-liner starting with javascript:) as a bookmarklet.
//   2. Invoke the bookmarklet by typing the name into the address bar on mobile.
//   3. On mobile, the cliboard should now contain the chrome-distiller:// URL.
//      If it does not, click the copy-to-clipboard button.
//   4. Paste into the URL bar and press Enter. Beware: "the link you copied" does *not* work,
//      as that will extract the http[s]:// part out of the URL instead of using the chrome-distiller:// URL.
//
// References
//   [hp01] https://gist.github.com/hughpearse/9454406ad43116dfaef8c43e7eac84f4
//   [w301] https://www.w3schools.com/howto/howto_js_copy_clipboard.asp

main();

function main() {
    // We cannot directly write to the clipboard without a user interaction context,
    // so an <input/><button/> pattern is used.
    //
    // On Chrome for Android, invoking a bookmarklet already enables
    // permissions for writing to the clipboard, so the function is invoked immediately.

    document.head.innerHTML = "";
    document.body.innerHTML =
        "<input id='reader-url-input' style='width: 100%'/>" +
        "<button id='reader-url-button' style='width: 100%'>Copy to Clipboard</button>";
    const inp = document.getElementById("reader-url-input");
    const but = document.getElementById("reader-url-button");
    inp.value = readerViewUrl();
    but.addEventListener("click", copyReaderUrl);
    copyReaderUrl();
    function copyReaderUrl() {
        inp.select();
        inp.setSelectionRange(0, 99999); // For mobile according to [w301]
        navigator.clipboard.writeText(inp.value);
    }
    // Changing the href will most likely fail due to security
    // policies of Chrome, but is left in case it works at some point.
    location.href = readerViewUrl();
}

function readerViewUrl() {
    // Code from [hp01], but simplified/reformatted.
    // The pattern "/"+"/" is a hack due to my minification script stripping out "//..." patterns.
    return "chrome-distiller:/"+"/00000000-0000-0000-0000-000000000000_"
          + sha256(window.location.href)
          + "/?url=" + window.location.href;
}



function customModulo(r, o) {
    // Unchanged from [hp01].
    const a = Math.floor(r / o);
    const t = r - o * a;
    return t
}

function sha256(o) {
    // Unchanged from [hp01].
    const r = sha256;
    function a(r, o) {
	return r >>> o | r << 32 - o
    }
    var t = Math.pow;
    var n = t(2, 32);
    var v = "length";
    var f, e;
    var c = "";
    var i = [];
    var u = o[v] * 8;
    var l = r.h = r.h || [];
    var s = r.k = r.k || [];
    var h = s[v];
    var d = {};
    for (var w = 2; h < 64; w++) {
	if (!d[w]) {
	    for (f = 0; f < 313; f += w) {
		d[f] = w
	    }
	    l[h] = t(w, .5) * n | 0;
	    s[h++] = t(w, 1 / 3) * n | 0
	}
    }
    o += "\x80";
    while (customModulo(o[v], 64) - 56) o += "\0";
    for (f = 0; f < o[v]; f++) {
	e = o.charCodeAt(f);
	if (e >> 8) return;
	i[f >> 2] |= e << customModulo(3 - f, 4) * 8
    }
    i[i[v]] = u / n | 0;
    i[i[v]] = u;
    for (e = 0; e < i[v];) {
	var M = i.slice(e, e += 16);
	var g = l;
	l = l.slice(0, 8);
	for (f = 0; f < 64; f++) {
	    var m = f + e;
	    var k = M[f - 15],
		S = M[f - 2];
	    var p = l[0],
		A = l[4];
	    if (f < 16) {
		M[f] = M[f]
	    } else {
		M[f] = M[f - 16] + (a(k, 7) ^ a(k, 18) ^ k >>> 3) + M[f - 7] + (a(S, 17) ^ a(S, 19) ^ S >>> 10) | 0
	    }
	    var C = l[7] + (a(A, 6) ^ a(A, 11) ^ a(A, 25)) + (A & l[5] ^ ~A & l[6]) + s[f] + M[f];
	    var b = (a(p, 2) ^ a(p, 13) ^ a(p, 22)) + (p & l[1] ^ p & l[2] ^ l[1] & l[2]);
	    l = [C + b | 0].concat(l);
	    l[4] = l[4] + C | 0
	}
	for (f = 0; f < 8; f++) {
	    l[f] = l[f] + g[f] | 0
	}
    }
    for (f = 0; f < 8; f++) {
	for (e = 3; e + 1; e--) {
	    var j = l[f] >> e * 8 & 255;
	    c += function() {
		if (j < 16) {
		    return "0" + j.toString(16)
		} else {
		    return j.toString(16)
		}
	    }()
	}
    }
    return c
};

@antoniocosta
Copy link

Confirmed as working. Nice and smart workaround! Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment