This page looks best with JavaScript enabled

Firefox, keep bookmark keywords

 ·  ☕ 12 min read

A few months ago, I wrote a post about using my URL bar as a CLI-like interface for navigating wikis. At the time I mentioned that the process was probably possible to replicate in Chrome, but I wasn’t sure how, because Chrome uses search engines where Firefox uses bookmark keywords. Now I’m pretty sure I was wrong - Chrome is incapable of providing anywhere near the functionality that Firefox can, and Firefox is just the better tool in every way when it comes to this use case - particularly for the JavaScript and mass update parts.

Unfortunately, I recently learned that keyword bookmarks are to be migrated into search engines, most probably in an attempt to make Firefox behave more similarly to Chrome. While I can understand the desire to support custom search engines with %s replacements, I feel that a total migration is deeply misguided, as Firefox’s paradigm is vastly superior to Chrome’s for multiple reasons.

Bookmark keywords are deeper functionality; search engines are just a special case and strict subset of them. And, as far as I’m aware, it is impossible to replicate the functionality I’m going to outline below via any method other than bookmark keywords. I would love to be wrong about this though! So please tell me either on Twitter or via some other contact method if you know of any other supported way to get this functionality. I’ll update this post if I learn of any other methods as well.

The next two sections are opinionated; you can skip them if you like.

Current lack of visibility of keywords

Keywords are not, and have never been, visible when adding a bookmark. If a user wants to add a keyword to a bookmark, they must first add the bookmark, then go back to edit the bookmark, and only now can they add the keyword. So if a user is adding keywords, they really want to have keywords on their bookmarks. A lack of adoption of the feature should not be blamed on the feature being bad, or confusing, or not fitting workflow/use cases/etc. It’s just completely invisible. Add Keyword to the default “Add bookmark” screen, maybe with a tooltip or help-text saying Type a keyword into the awesomebar to navigate to your bookmark and see if people start to use the feature.

This is a really, really, really, really big deal

And on a complementary note, users who DO have keywords are likely to be extremely reliant on their use of said keywords, due to the nature of how difficult it is to find keywords in the first place. This isn’t a thing to just say “oh you’ll get used to it.” Three (four?) years after the “tab-specific history” menu was removed from MouseGestures because it was never supported in WebExtensions API, I’m still reaching for right-click + scroll wheel subconsciously to scroll through my tab history, and that was a drop in the bucket compared to this.

I would guess that somewhere between 10% and 30% of my total URL changes are done via bookmark keywords. My primary workflow navigation is built around bookmark keywords. I literally structured part of my code base on the assumption that I could search it using bookmark keywords (see the last paragraph of that article). Sure, I’m in the pretty unique position of developing a code base that exists entirely online (a MediaWiki wiki), and so very indepth searching tooling in my browser is gonna be essential, but I also use bookmark keywords for casual use - it’s how I navigate for video game references, reddit, Twitter, Instagram, etc. Literally everything.

If you didn’t already read my Your URL is a CLI post I’d suggest reading that too - it describes my entire (admittedly kinda insane) workflow and does describe how much my life (and by extension everyone I work with who I’ve convinced to use a similar system) really does revolve around these things.

Desired functionality

A screenshot of my bookmarks folder

The functionality that I’m concerned will be missing from search terms is as follows:

  1. Mass management. I have about 30 JavaScript-based bookmarklets with keywords and over 1000 “traditional” bookmarklets with keywords (a mix of direct links and single-substitution searches or direct-substitution links). I name my bookmarklets so that the name matches the keyword, and put them carefully into folders. Other people “subscribe” to my bookmarks and are able to find and use them only because of this organization. How will this work with search engines?
  2. Mass editing. At the same time, I need to be able to edit these bookmarks in bulk. Currently I do that by maintaining the exported HTML as my “source of truth” of my bookmark keywords, updating it with regex, and then re-importing when I make changes. How possible will this be with search engines?
  3. The ability to execute JavaScript by typing a keyword into the URL bar followed by additional arguments that are then passed to said JavaScript. This is the most important thing, and the focus of the rest of the blog post is talking about how GODDAMN COOL this is - the functionality you can get by doing this is, in my opinion, one of the most criminally underused elements of browsers currently. This should be doable without having to press any extra up/down arrow keys, or waiting for extra UI loading time, or using the mouse, or opening an additional UI element besides the normal URL bar, or pressing any additional keys beyond my keyword plus URL params. Just these keystrokes, nothing else.

Bookmarklets WITH KEYWORDS can do anything right now

All of the code in the following sections is originally adapted from this LifeHacker article from 2007, back when bookmarklets were trendy. Before Google Chrome decided they weren’t cool enough to be talked about I guess? I don’t know, why don’t people talk about bookmarklets anymore?

Multiple-substitution keywords

Back in pre-Quantum Firefox, there was this glorious extension called URLAlias. It let you configure, well, URL aliases. This was actually just bookmark keywords. But I didn’t know about bookmark keywords at the time (as I mentioned before, they’re kinda hidden), so I used this extension instead. Notably, though, it let you have arbitrarily many %s substitutions in your alias, unlike normal bookmarks with keywords. I described this bookmarklet in my Your URL bar is a CLI post, so I won’t go over the code again here, but here’s the bookmarklet:

javascript:%20%20var%20s='%s';%20url='https://%s.gamepedia.com/index.php?search=%s&title=Special:Search';%20tt=[''];%20j=0;%20qc=0;%20chunks=url.split('%s');%20for(i=0;%20i<s.length;%20i++){%20if(s.charAt(i)=='%22')qc=qc^1;%20if%20(s.charAt(i)=='%20'&&(qc||j>=chunks.length-2))%20{%20tt[j]+=('^');%20}%20else%20if%20(s.charAt(i)=='%20'){%20j++;%20tt.push('%20');%20}%20else%20{%20tt[j]+=(s.charAt(i));%20}%20}%20t%20=%20tt.join('');%20args=t.split(/\s/);%20nurl='';%20for(i=0;%20i<chunks.length;%20i++){%20nurl+=chunks[i];%20%20if(args[i]!=undefined)%20{%20args[i]=args[i].replace(/\^/g,'%20');%20nurl+=args[i];%20}%20}%20location.replace(nurl,'<\%20BR>');

The URL in this specific one, https://%s.gamepedia.com/index.php?search=%s&title=Special:Search, has two substitutions: the first %s picks the Gamepedia wiki of your choosing, and the second gives it a search term.

Namespace lookup

So in a variation on the above, there’s a “special page” in MediaWiki called AllPages where you want to look up all pages in a certain “namespace.” Namespaces are prefixed by a name visible to the user but internally keyed by an integer. Naturally, users tend to remember the name, and the only integer numbers I remember off the top of my head are module is 828 and template is 10. Unfortunately, the URL for Special:AllPages uses integer keys rather than human-readable names. But we could easily modify our bookmarklet above to take care of that for us! The set of namespaces can change over time, but it’s pretty static.

I actually mentioned this in my previous blog post as an application I was considering doing but never got around to, but this post was enough motivation for me to finally write it, so here we go.

While this particular example is very MediaWiki-specific and so perhaps not broadly applicable, you can imagine doing this for any workplace scenario where you have a small set of UUIDs that correspond to human-readable names, with the UUIDs used in URLs. If the human names have spaces, put underscores. If you need to do two replacements in your URL, you can easily modify the code to make that work too.

This setup is most useful when there’s more than one replacement in the URL, since if there was just a single one you could in theory write a code generator to create all of the individual bookmarklets instead of writing the bookmarklet…but the bookmarklet itself is more extensible than a code generator would be so I wouldn’t judge you for creating a version with just one replacement (as I even did).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(function () {
	let s = "%s";
	let url = "https://lol.gamepedia.com/Special:AllPages?namespace=%s";
	let r = 0; // which input we replace in the lookup table, here there's only one 
	let l = {
		main:'0',
		talk:'1',
		user:'2',
		project:'4',
		file:'6',
		mediawiki:'8',
		template:'10',
		help:'12',
		category:'14',
		form:'106',
		widget:'274'
	};
	let tt = [""];
	let j = 0;
	let qc = 0;
	let chunks = url.split("%s");
	for (i = 0; i < s.length; i++) {
		if (s.charAt(i) == "%22") qc = qc ^ 1;
		if (s.charAt(i) == " " && (qc || j >= chunks.length - 2)) {
			tt[j] += "^";
		} else if (s.charAt(i) == " ") {
			j++;
			tt.push(" ");
		} else {
			tt[j] += s.charAt(i);
		}
	}
	let t = tt.join("");
	let args = t.split(/\s/);
	let nurl = "";
	for (i = 0; i < chunks.length; i++) {
		nurl += chunks[i];
		if (args[i] != undefined) {
			if (i == r) {
				args[i] = l[args[i].toLowerCase()] || args[i];
			}
			args[i] = args[i].replace(/\^/g, " ");
			nurl += args[i];
		}
	}
	location.replace(nurl, "< BR>");
})();

Minified as a bookmarklet:

javascript:(function %20() %20{ %20let %20s="%s"; %20let %20url="https://lol.gamepedia.com/Special:AllPages?namespace=%s"; %20let %20r=0; %20let %20l={ %20main:'0', %20talk:'1', %20user:'2', %20project:'4', %20file:'6', %20mediawiki:'8', %20template:'10', %20help:'12', %20category:'14', %20form:'106', %20widget:'274' %20}; %20tt=['']; %20j=0; %20qc=0; %20chunks=url.split("%s"); %20for(i=0; %20i<s.length; %20i++){ %20if(s.charAt(i)=='%22')qc=qc^1; %20if %20(s.charAt(i)==' %20'&&(qc||j>=chunks.length-2)) %20{ %20tt[j]+=('^'); %20} %20else %20if %20(s.charAt(i)==' %20'){ %20j++; %20tt.push(' %20'); %20} %20else %20{ %20tt[j]+=(s.charAt(i)); %20} %20} %20t %20= %20tt.join(''); %20let %20args=t.split(/\s/); %20let %20nurl=""; %20for %20(i=0;i<chunks.length;i++){ %20nurl+=chunks[i]; %20if %20(args[i]!=undefined){ %20if %20(i==r) %20{ %20args[i]=l[args[i].toLowerCase()]||args[i]; %20} %20args[i]=args[i].replace(/\^/g," %20"); %20nurl+=args[i]; %20} %20} %20location.replace(nurl,"< %20BR>");})();

So here’s an example with two replacements, we just modify these two lines to change what we’re replacing. Similar to above, this would now let you go to Special:AllPages in the namespace of your choosing on any Gamepedia wiki (type the wiki name first).

1
2
	let url = "https://%s.gamepedia.com/Special:AllPages?namespace=%s";
	let r = 1; // in this case we replace the 2nd user param in the lookup table

Minified as a bookmarklet:

javascript:(function %20() %20{ %20let %20s="%s"; %20let %20url="https://%s.gamepedia.com/Special:AllPages?namespace=%s"; %20let %20r=1; %20let %20l={ %20main:'0', %20talk:'1', %20user:'2', %20project:'4', %20file:'6', %20mediawiki:'8', %20template:'10', %20help:'12', %20category:'14', %20form:'106', %20widget:'274' %20}; %20tt=['']; %20j=0; %20qc=0; %20chunks=url.split("%s"); %20for(i=0; %20i<s.length; %20i++){ %20if(s.charAt(i)=='%22')qc=qc^1; %20if %20(s.charAt(i)==' %20'&&(qc||j>=chunks.length-2)) %20{ %20tt[j]+=('^'); %20} %20else %20if %20(s.charAt(i)==' %20'){ %20j++; %20tt.push(' %20'); %20} %20else %20{ %20tt[j]+=(s.charAt(i)); %20} %20} %20t %20= %20tt.join(''); %20let %20args=t.split(/\s/); %20let %20nurl=""; %20for %20(i=0;i<chunks.length;i++){ %20nurl+=chunks[i]; %20if %20(args[i]!=undefined){ %20if %20(i==r) %20{ %20args[i]=l[args[i].toLowerCase()]||args[i]; %20} %20args[i]=args[i].replace(/\^/g," %20"); %20nurl+=args[i]; %20} %20} %20location.replace(nurl,"< %20BR>");})();

YouTube search – but only before when it’s almost bedtime!

Ok so this one I’m not actually using, it’s just a theoretical example…but in this case I made a bookmarklet that warns you if you try to search YouTube after a time that you set as your (almost) bedtime. It’s not a blocker, just a soft “hey it’s almost bedtime, are you sure?” If you’re not sure, it doesn’t complete the search, that’s it. Assuming you’re like me and do all your searches with ctrl+L then y $searchTerm this should save you from most visits. Note this could also be applied to any other bookmark you like, but the user-input part is the part where they keyword part of the bookmarklet is mandatory to its functionality.

Yeah, sure, there are addons that do stuff like this for you, but this is a pretty low-tech approach. Do with it as you like :)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(function() {
	let now = new Date();
	let bedtime = new Date();
	bedtime.setHours(22);
	if (bedtime < now || confirm("It's almost bedtime! Are you sure?")) {
		var s = "%s";
		url = "https://www.youtube.com/results?search_query=%s";
		tt = [""];
		j = 0;
		qc = 0;
		chunks = url.split("%s");
		for (i = 0; i < s.length; i++) {
			if (s.charAt(i) == "%22") qc = qc ^ 1;
			if (s.charAt(i) == " " && (qc || j >= chunks.length - 2)) {
				tt[j] += "^";
			} else if (s.charAt(i) == " ") {
				j++;
				tt.push(" ");
			} else {
				tt[j] += s.charAt(i);
			}
		}
		t = tt.join("");
		args = t.split(/\s/);
		nurl = "";
		for (i = 0; i < chunks.length; i++) {
			nurl += chunks[i];
			if (args[i] != undefined) {
				args[i] = args[i].replace(/\^/g, " ");
				nurl += args[i];
			}
		}
		location.replace(nurl, "< BR>");
	}
});
javascript:(function()%20{%20var%20now%20=%20new%20Date();%20var%20bedtime%20=%20new%20Date();%20bedtime.setHours(22);%20if%20(bedtime%20>%20now%20||%20%20confirm("It's%20almost%20bedtime!%20Are%20you%20sure?"))%20{%20%20var%20s='%s';%20url='https://www.youtube.com/results?search_query=%s';%20tt=[''];%20j=0;%20qc=0;%20chunks=url.split("%s");%20for(i=0;%20i<s.length;%20i++){%20if(s.charAt(i)=='%22')qc=qc^1;%20if%20(s.charAt(i)=='%20'&&(qc||j>=chunks.length-2))%20{%20tt[j]+=('^');%20}%20else%20if%20(s.charAt(i)=='%20'){%20j++;%20tt.push('%20');%20}%20else%20{%20tt[j]+=(s.charAt(i));%20}%20}%20t%20=%20tt.join('');%20args=t.split(/\s/);%20nurl='';%20for(i=0;%20i<chunks.length;%20i++){%20nurl+=chunks[i];%20%20if(args[i]!=undefined)%20{%20args[i]=args[i].replace(/\^/g,'%20');%20nurl+=args[i];%20}%20}%20location.replace(nurl,'<\%20BR>');}})();

In this bookmarklet I rewrite Twitter’s slightly random advanced search to have syntax that makes more sense to me. I use the keyword tsa (for Twitter search advanced) and might type for example tsa --all:cute --any:kittens OR PUPPIES --before:2020-01-01 to get cute puppies or kittens before 2020.

If you read my other post that I’ve linked a few times already, you’ll notice this is similar to the example I gave in “but why not a true CLI” with flags and stuff - well, turns out splitting on -- is actually pretty easy, and Twitter’s advanced search is a great use case for this because their advanced search UI is actually terrible, and the URL patterns are pretty hard to remember.

I didn’t add error handling (it just fails) or a help alert if you type --help but as a proof of concept it seems pretty nice and I’m sure I’ll use it, and add these things when I inevitably fail to get things right the first time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function () {
	let s = "%s";
	let url = "https://twitter.com/search?q=%s";
	let l = {
		all:'%s',
		exact:'"%s"',
		from:"(from:%s)",
		any:"(%s)",
		not:"-%s",
		to:"(to:%s)",
		mentions:"@%s",
		hashtag:"(#%s)",
		before:"until:%s",
		after:"since:%s",
	};
	let chunks = url.split("%s");
	let args = s.split("--");
	let nurl = chunks[0];
	let p = [];
	for (j=1; j<args.length; j++) {
		arg=args[j];
		arg=arg.replace(/\^/g, " ");
		w=arg.match(/\w+/);
		arg=arg.replace(/\w+:/, "");
		p.push(l[w].replace('%s', arg.trim()));
	}
	nurl += p.join(' ');
	if (chunks[1]) nurl+= chunks[1];
	location.replace(nurl, "< BR>");
})();
javascript:(function%20()%20{%20%20let%20s%20=%20"%s";%20%20let%20url%20=%20"https://twitter.com/search?q=%s";%20%20let%20l%20=%20{%20%20all:'%s',%20%20exact:'"%s"',%20%20from:"(from:%s)",%20%20any:"(%s)",%20%20not:"-%s",%20%20to:"(to:%s)",%20%20mentions:"@%s",%20%20hashtag:"(#%s)",%20%20before:"until:%s",%20%20after:"since:%s",%20%20};%20%20let%20chunks%20=%20url.split("%s");%20%20let%20args%20=%20s.split("--");%20%20let%20nurl%20=%20chunks[0];%20%20let%20p%20=%20[];%20%20for%20(j=1;%20j<args.length;%20j++)%20{%20%20arg=args[j];%20%20arg=arg.replace(/\^/g,%20"%20");%20%20w=arg.match(/\w+/);%20%20arg=arg.replace(/\w+:/,%20"");%20%20p.push(l[w].replace('%s',%20arg.trim()));%20%20}%20%20nurl%20+=%20p.join('%20');%20%20if%20(chunks[1])%20nurl+=%20chunks[1];%20%20location.replace(nurl,%20"<%20BR>");%20})();

Conclusion

Bookmark keywords are the best part of Firefox. (UserChrome.css is only the second-best part of Firefox.) You can do so very many cool and creative things with JavaScript-based bookmark keywords that take arguments! This is the best way to browse the web. I really don’t think Chrome’s approach of search engines is the right way to do things - I can understand why it seems more user friendly, and I guess I agree with the idea of having it as well, but I don’t think it handles enough of the use cases of bookmark keywords to replace them. Bookmark keywords need to stay.

Share on

river
WRITTEN BY
River
River is a developer most at home in MediaWiki and known for building Leaguepedia. She likes cats.


What's on this Page