2018-10-19, updated: 2024-03-12
Tested with Nyxt 2.0.0.
Hooks in practice
Update: This article was updated for Nyxt 2 for which the hook system has been completely rewritten.
Using hooks to smart-program your browser
Hooks are a great way to extend a workflow by triggering actions upon events. Simply put, a hook is an object that holds a list of handlers, which are functions called when the hook is run at a precisely defined point in the program.
In the world of a web browser, there are a ton of events: page loaded, DOM available, page rendered, etc. In addition to the events fired off by normal processing of web pages there are a large set of events which include actions by the user: tab deleted, page bookmarked, command called, mode enabled, etc.
In Nyxt, all these events are hookable.
Hooking into the events fired off by the browser or by the user allows the creation of extendable and tailored workflows.
The Nyxt hook system
Many hooks are as such executed at different points in Nyxt:
Global hooks, such as the
*after-init-hook*
hook, which is run after the browser has been initialized.Window and buffer related hooks (before and after creation, before and after the page is loaded, etc.).
Modes hooks (before and after a mode is enabled or disabled).
"Before" and "after" command hooks.
For the full list, run describe-variable
and describe-slot
over the names ending with -hook
.
Practical examples
Let's consider a practical example. If you want to force the redirection of a domain to another, you can use the request-resource-hook
to change the URL and return a new one. In the example below, we make sure we always visit old.reddit.com
instead of the new Reddit interface:
(defun old-reddit-handler (request-data)
(let ((uri (url request-data)))
(setf (url request-data)
(if (search "reddit.com" (quri:uri-host uri))
(progn
(setf (quri:uri-host uri) "old.reddit.com")
(log:info "Switching to old Reddit: ~s" (object-display uri))
uri)
uri)))
request-data)
(define-configuration buffer
((request-resource-hook
(reduce #'hooks:add-hook
(mapcar #'make-handler-resource (list #'old-reddit-handler))
:initial-value %slot-default%))))
You can ask Nyxt to automatically enable or disable modes depending on the URL, for instance, you can toggle the proxy mode per domain, which can be very convenient if you would like to, say, disable Tor for some resource intensive domains.
Another cool example would be automatically downloading any YouTube video we visit:
(defvar +youtube-dl-command+ "youtube-dl"
"Path to the 'youtube-dl' program.")
(defun auto-yt-dl-handler (request-data)
"Download a Youtube URL asynchronously to /tmp/videos/.
Videos are downloaded with `+youtube-dl-command+'."
(let ((url (url request-data)))
(if (and url
(member (quri:uri-domain url) '("youtube.com" "youtu.be")
:test #'string=)
(string= (quri:uri-path url) "/watch"))
(progn
(echo "Youtube: downloading ~a" url)
(uiop:launch-program (list +youtube-dl-command+ (quri:render-uri url) "-o" "/tmp/videos/%(title)s.%(ext)s"))
;; Return nil to cancel the page load.
nil)
request-data)))
(define-configuration buffer
((request-resource-hook
(reduce #'hooks:add-hook
(mapcar #'make-handler-resource (list #'auto-yt-dl-handler))
:initial-value %slot-default%))))
Adjust it to your taste!
All user commands have hooks
One feature that makes Nyxt unique is the ability to extend commands exposed to the user.
Because the Common Lisp language allows it, one could replace a command definition by another function. This is however not the recommended approach, most notably because it could break the built-in behavior.
We can then use the "before" and "after" hooks to extend the built-in commands. It is as simple as defining a new function with no parameters:
(defun post-bookmark-hook ()
;; Your bookmark synchronization code...
(log:info "Let's sync the bookmarks!"))
and adding it to the list of hooks:
And voila!
Conclusions
In summary, hooks present a very simple and effective mechanism to chain behavior in your workflows. We are looking forward to seeing what you can create with them!
For more about details on the hook internals and advanced usage, see our other article.
Thanks for reading!
Did you enjoy this article? Register for our newsletter to receive the latest hacker news from the world of Lisp and browsers!
- Maximum one email per month
- Unsubscribe at any time