/emacs-webkit

An Emacs Dynamic Module for WebKit, aka a fully fledged browser inside emacs

Primary LanguageCGNU General Public License v3.0GPL-3.0

emacs-webkit

Update

I haven’t had time since originally creating this to work further on it. Meanwhile bugs with xwidget on pgtk have been smoothed out upstream and some minimal maintenance continues to happen there. Thus if you want a browser in emacs, I recommend building with xwidgets (which also supports macos). I don’t plan to continue any development on this for both technical and personal reasons, but I think it represents an interesting (albeit somewhat hacky) approach to pushing what is possible with emacs dynamic modules.

Warning 1!

This package is still in the early stages of active development. While I am currently using it daily, it should be considered as still in an alpha stage. This means that the public interface can and likely will change! Also, due to the nature of dynamic modules dealing with memory directly, bugs can mean segfaults, memory leaks, and ultimately untimely crashes. So beware that data loss may result! However, if you do experience crashes, please report it!

Warning 2!

Browsers are a big target of malware and as such ensuring you’re running the most up to date version that receives security patches is essential. It is up to you to ensure you are running an up to date version of webkitgtk2. Note that not every distro consistently and quickly updates webkitgtk2 in its package repos. The WebKitGTK team publishes releases and security advisories here.

Running a browser capable of javascript execution inside Emacs is potentially scary since inside Emacs, all code is trusted and usually has unrestricted access to your system. This is somewhat mitigated by webkitgtk2 using a multiprocess model where website’s content and scripts run in a separate process from UI, which in this case is Emacs. I’ll make every effort to ensure that potentially malicious JavaScript cannot remotely execute arbitrary lisp, however my trust model will always treat user lisp code as trusted. Furthermore I make no grantees about the overall security of emacs-webkit.

Background

I’ve found that the only two applications I regularly have open are Emacs and a browser. Emacs is a joy to use while I feel like I’m constantly fighting my browser. I used to be pretty happy with Firefox and vimperator however since Project Quantum came to FF 57+, I haven’t been satisfied with the extensibility or speed of the WebExtension replacements. There are a few keyboard driven, extensible browser projects such as qutebrowser, LuaKit, vimb, surf, etc.... Unfortunately given the complexity of the “modern” web, they all have to be based off of either the Blink (through QtWebEngine) or WebKit (through WebKitGTK) browser engines (I really wish Mozilla would put more effort into making a clean and maintained API for embedding the latest Gecko with rust components, i.e. servo). I shy away from Blink based browsers out of principles of wanting to avoid Google and the browser monoculture and also because the only easy way to embed Blink is through QtWebEngine and I don’t really like Qt all that much.

An attempt at adding a webkitgtk widget to Emacs with the experimental xwidgets feature was made many years ago and has received pretty minimal development over the years. I think it isn’t for lack of interest in the concept, but rather the difficulty of understanding the complicated dance that is Emacs redisplay and how xwidgets hacked themselves into that. Also the “politics” of having a full featured browser inside Emacs core that can potentially execute non-free javascript usually means much discussion on emacs-devel when trying to add new features. I figured that perhaps such a feature could be implemented instead as a dynamic module. This has the advantage of a clearer separation between Emacs’ display handling and webkit’s, hopefully making it easier to workout the inevitable bugs that occur when forcing them to coexist. This also allows features and fixes to be developed outside of Emacs core with less concern for supporting all the platforms and environments that Emacs needs to work with, while also avoiding some of the “political tensions”.

emacs-webkit now has all of the features that the webkit xwidget has, plus more, such as integrating some features from ~xwwp~. I was also able to add experimental support for opening an non-embedded, dedicated webkit window. In principle this allows running emacs-webkit on a tty as webkit popus up its own window (of course one needs a graphical session to be running, i.e. $DISPLAY needs to be set). In testing this, I’ve found there will always be behavior out of Emacs’ control due to the window being at the mercy of the window manager, so things like focusing end up wonky and harder to control. To try this out: (setq webkit-own-window t).

But what about the Emacs Application Framework (EAF)?

While I think its neat what EAF has been able to do, I personally have less of a desire to dive into its code due to its reliance on Qt (and hence Blink for its browser component) and python, and I suspect this may be a barrier for others as well. Furthermore, I don’t think it has a technical path forward to natively work on Wayland due to its current reliance on the XEmbed protocol. My goal with emacs-webkit was to have something that will work with pgtk port of Emacs (in fact I primarily developed it on Wayland running pgtk Emacs).

But what about nyxt?

I think nyxt is certainly a cool project and I wish them the best! However, I would say this project is for the Emacs user like me who begrudgingly uses a modern web browser but wishes they didn’t have to. I wish I could make eww my default browser but it just very often doesn’t cut it due to the unfortunate world of “modern” javascript and “web apps”. In contrast, I would say nyxt is more for the lisp aficionado who wishes the web ran lisp instead of javascript and the Emacs user who wishes Emacs’ underlying UI paradigm looked more like the web’s DOM. I believe the nyxt developers want nyxt to essentially be a common lisp emacs. It is a massive undertaking and will take time for them to build an ecosystem like the one Emacs has developed over the decades. I’ve thought about how I could make nyxt integrate with Emacs in a way I would be happy with and I’ve found that while it is certainly possible to do so given nyxt’s extensibility, I felt like there would always be some friction. For example who’s UI should I use? Do I integrate Emacs buffer list into nyxt’s minibuffer or nyxts buffer list into Emacs? Finally I wanted an excuse to dig more into Emacs’ C guts and this project has given me a lot of chances to do so.

Installation

Once things stabilize a bit, I’ll probably package this for MELPA.

emacs-webkit requires at least Emacs 28

Make sure you have gcc, pkg-config, gtk3, glib-networking, and of course webkitgtk installed. Then just run make to make webkit-module.so.

Some package managers support custom build steps to automate building. For example with the straight.el develop branch you can use this recipe

(straight-use-package
 '(webkit :type git :host github :repo "akirakyle/emacs-webkit"
          :branch "main"
          :files (:defaults "*.js" "*.css" "*.so")
          :pre-build ("make")))

I’m a bit hesitant to add lisp code to do this automagically or fetch prebuilt modules from the web like pdf-tools or emacs-libvterm, because I’m a believer that it should be the job of a package manager, but perhaps I’ll be convinced otherwise.

Setup

First ensure emacs-webkit is on your load-path.

Manually

(require 'webkit) 
(global-set-key (kbd "s-b") 'webkit) ;; Bind to whatever global key binding you want if you want
(require 'webkit-ace) ;; If you want link hinting
(require 'webkit-dark) ;; If you want to use the simple dark mode

use-package

(use-package webkit
  :bind ("s-b" 'webkit)) ;; Bind to whatever global key binding you want if you want
(use-package 'webkit-ace) ;; If you want link hinting
(use-package 'webkit-dark) ;; If you want to use the simple dark mode

Usage

  • M-x webkit
  • Enter url or keywords to search
  • C-h m (describe-mode) to see keybindings.
  • Feel the power (and weight) of a browser running inside Emacs.
  • Emacs’ builtin bookmarks and org-store-link are supported!

emacs-webkit has a concept of an “insert” mode, which moves keyboard focus to the webview from Emacs. This means the webview will see all key-presses and Emacs will only see the modifier keypresses that are unhandled by the webview. This is useful for typing in a text box or using the keyboard shortcuts a website might set up. To return focus back to Emacs use C-g. Some emacs-webkit features might have a javascript component that requires moving to insert mode. Sometimes javascript is buggy or crashes in which case you may be left surprised that Emacs isn’t responding to you. C-g is, as always, your friend here.

webkit-start-web-inspector will start webkit’s built in dev tools. Beware that C-g cannot escape from web inspector’s focus but C-<tab> appears to return focus to the webkit view (there doesn’t appear to be much emac-webkit can do about this).

Customization

;; If you don't care so much about privacy and want to give your data to google
(setq webkit-search-prefix "https://google.com/search?q=") 

;; Specify a different set of characters use in the link hints
;; For example the following are more convienent if you use dvorak
(setq webkit-ace-chars "aoeuidhtns")

;; If you want history saved in a different place or
;; Set to `nil' to if you don't want history saved to file (will stay in memory)
(setq webkit-history-file "~/path/to/webkit-history") 

;; If you want cookies saved in a different place or
;; Set to `nil' to if you don't want cookies saved
(setq webkit-cookie-file "~/path/to/cookies")

;; See the above explination in the Background section
;; This must be set before webkit.el is loaded so certain hooks aren't installed
(setq webkit-own-window t) 

;; Set webkit as the default browse-url browser
(setq browse-url-browser-function 'webkit-browse-url)

;; Force webkit to always open a new session instead of reusing a current one
(setq webkit-browse-url-force-new t)

;; Globally disable javascript
(add-hook 'webkit-new-hook #'webkit-enable-javascript)

;; Override the "loading:" mode line indicator with an icon from `all-the-icons.el'
;; You could also use a unicode icon like ↺
(defun webkit--display-progress (progress)
  (setq webkit--progress-formatted
        (if (equal progress 100.0)
            ""
          (format "%s%.0f%%  " (all-the-icons-faicon "spinner") progress)))
  (force-mode-line-update))

;; Set action to be taken on a download request. Predefined actions are
;; `webkit-download-default', `webkit-download-save', and `webkit-download-open'
;; where the save function saves to the download directory, the open function
;; opens in a temp buffer and the default function interactively prompts.
(setq webkit-download-action-alist '(("\\.pdf\\'" . webkit-download-open)
                                     ("\\.png\\'" . webkit-download-save)
                                     (".*" . webkit-download-default))

;; Globally use a proxy
(add-hook 'webkit-new-hook (lambda () (webkit-set-proxy "socks://localhost:8000")))

;; Globally use the simple dark mode
(setq webkit-dark-mode t)

I personally use evil so I’ve included evil-collection bindings which I hope to upstream at some point when things stabilize.

(use-package evil-collection-webkit
  :config
  (evil-collection-xwidget-setup))

Roadmap (roughly in order of my priorities)

  • Ad block
  • Edit text areas in temp emacs buffer
  • Pass integration
  • XEmbed when using emacs --with-x
  • Browsing sessions/data and better cookie management
  • Web extensions?
  • Echo url on mouse hover
  • completing-read link completion/heading jumping
  • History display-table mode
  • favicon on mode line