Bookmarklets come in the form of javascript:
URLs and are, as the name suggests, URLs.1
This means that the usual rules of URL encoding and special characters restrictions apply.
Let’s consider your bookmarklet: javascript: let s = '%s'; let a = 'a%20b'; let b = 'a b'; a = a.replace(/%20/g, ' '); alert(a); b = encodeURIComponent(b); b = b.replace(/%20/g,' '); alert(b);
.
When the bookmarklet is executed, it’s parsed as a URL and the javascript:
scheme is stripped.
During parsing, the script is decoded:
%s'
is not a valid percent-encoding sequence.
%20
is the encoding for a
(space).
decodeURI
will throw a “URIError
: malformed URI sequence”.
Very likely, Safari won’t execute this script.
Firefox and Chrome seem to ignore the invalid encoding sequence and treat it as a plain %
.
That’s quite unsafe, because a valid sequence would — perhaps unexpectedly — be correctly decoded.
But everything else is the same: the resulting script is this: let s = '%s'; let a = 'a b'; let b = 'a b'; a = a.replace(/ /g, ' '); alert(a); b = encodeURIComponent(b); b = b.replace(/ /g,' '); alert(b);
.
Note how there is no literal %20
in your script anymore.
Since you need the %20
literally, you have to encode the script as a URL first:
const script = `let s = '%s';
let a = 'a%20b';
let b = 'a b';
a = a.replace(/%20/g, ' ');
alert(a);
b = encodeURIComponent(b);
b = b.replace(/%20/g, ' ');
alert(b);`;
console.log(encodeURI(`javascript:${script}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }
.as-console-row-code { word-break: break-all; }
encodeURI
will encode your script in a way that is guaranteed to work as a bookmarklet, and, in particular, encodes %
as %25
, although it encodes a few special characters perhaps unnecessarily, e.g. "
, `
, [
, ]
, {
, }
, <
, >
, \
, |
, spaces, line breaks.
I don’t actually know how all browsers deal with these special characters in bookmarklets, so it’s probably safest to trust this output and use this as your bookmarklet URL:
javascript:let%20s%20=%20'%25s';%0Alet%20a%20=%20'a%2520b';%0Alet%20b%20=%20'a%20b';%0Aa%20=%20a.replace(/%2520/g,%20'%20');%0Aalert(a);%0Ab%20=%20encodeURIComponent(b);%0Ab%20=%20b.replace(/%2520/g,%20'%20');%0Aalert(b);
But please consider using a userscript manager instead.
Bookmarklets are cumbersome and they don’t work on sites that block them using Content Security Policy (CSP) rules.
1: Could’ve jokingly written “not URLs” at the end, because technically they’re not Uniform Resource Locators, because they’re JavaScript code; they don’t locate anything. They’re also neither URIs nor URNs because they’re neither identifiers nor names. The javascript:
scheme is kind of a hack, because it doesn’t really fit into this taxonomy. They only behave as URLs because the JavaScript code is entered into the URL field of a Bookmark entry.