-1

If I run this code in a JS fiddle, everything works as expected :

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);

It returns a b a b

But if I run the same in a bookmarlet :

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);

It returns a b a%20b, while I would expect a b a b

What is going wrong here ?

CurioUser
  • 3
  • 3
  • 2
    A bookmarklet is essentially a URL. In URLs, `%20` is a space, so you need to encode the `%` signs as `%25`. Consider using a userscript manager instead. – Sebastian Simon Nov 01 '22 at 00:04
  • I guess I’ll write an answer. The closest thing to a duplicate target I could find was [Safari 5 changes on use of javascript bookmarklets?](/q/7324022/4642212), but that doesn’t really fit. – Sebastian Simon Nov 01 '22 at 00:13

1 Answers1

0

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.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75