aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/misc/eww.texi9
-rw-r--r--etc/NEWS13
-rw-r--r--lisp/net/eww.el123
-rw-r--r--test/lisp/net/eww-tests.el179
4 files changed, 312 insertions, 12 deletions
diff --git a/doc/misc/eww.texi b/doc/misc/eww.texi
index 5e69b11d347..d31fcf1802b 100644
--- a/doc/misc/eww.texi
+++ b/doc/misc/eww.texi
@@ -192,6 +192,15 @@ history press @kbd{H} (@code{eww-list-histories}) to open the history
192buffer @file{*eww history*}. The history is lost when EWW is quit. 192buffer @file{*eww history*}. The history is lost when EWW is quit.
193If you want to remember websites you can use bookmarks. 193If you want to remember websites you can use bookmarks.
194 194
195@vindex eww-before-browse-history-function
196 By default, when browsing to a new page from a ``historical'' one
197(i.e.@: a page loaded by navigating back via @code{eww-back-url}), EWW
198will first delete any history entries newer than the current page. This
199is the same behavior as most other web browsers. You can change this by
200customizing @code{eww-before-browse-history-function} to another value.
201For example, setting it to @code{ignore} will preserve the existing
202history entries and simply prepend the new page to the history list.
203
195@vindex eww-history-limit 204@vindex eww-history-limit
196 Along with the URLs visited, EWW also remembers both the rendered 205 Along with the URLs visited, EWW also remembers both the rendered
197page (as it appears in the buffer) and its source. This can take a 206page (as it appears in the buffer) and its source. This can take a
diff --git a/etc/NEWS b/etc/NEWS
index fd957fdb115..745b3b12936 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1018,6 +1018,19 @@ When invoked with the prefix argument ('C-u'),
1018This is useful for continuing reading the URL in the current buffer 1018This is useful for continuing reading the URL in the current buffer
1019when the new URL is fetched. 1019when the new URL is fetched.
1020 1020
1021---
1022*** History navigation in EWW now works like other browsers.
1023Previously, when navigating back and forward through page history, EWW
1024would add a duplicate entry to the end of the history list each time.
1025This made it impossible to navigate to the "end" of the history list.
1026Now, navigating through history in EWW simply changes your position in
1027the history list, allowing you to reach the end as expected. In
1028addition, when browsing to a new page from a "historical" one (i.e. a
1029page loaded by navigating back through history), EWW deletes the history
1030entries newer than the current page. To change the behavior when
1031browsing from "historical" pages, you can customize
1032'eww-before-browse-history-function'.
1033
1021** go-ts-mode 1034** go-ts-mode
1022 1035
1023+++ 1036+++
diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index 5a25eef9e3c..2936bc8f099 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -182,6 +182,33 @@ the tab bar is enabled."
182 (const :tag "Open new tab when tab bar is enabled" tab-bar) 182 (const :tag "Open new tab when tab bar is enabled" tab-bar)
183 (const :tag "Never open URL in new tab" nil))) 183 (const :tag "Never open URL in new tab" nil)))
184 184
185(defcustom eww-before-browse-history-function #'eww-delete-future-history
186 "A function to call to update history before browsing to a new page.
187EWW provides the following values for this option:
188
189* `eww-delete-future-history': Delete any history entries after the
190 currently-shown one. This is the default behavior, and works the same
191 as in most other web browsers.
192
193* `eww-clone-previous-history': Clone and prepend any history entries up
194 to the currently-shown one. This is like `eww-delete-future-history',
195 except that it preserves the previous contents of the history list at
196 the end.
197
198* `ignore': Preserve the current history unchanged. This will result in
199 the new page simply being prepended to the existing history list.
200
201You can also set this to any other function you wish."
202 :version "30.1"
203 :group 'eww
204 :type '(choice (function-item :tag "Delete future history"
205 eww-delete-future-history)
206 (function-item :tag "Clone previous history"
207 eww-clone-previous-history)
208 (function-item :tag "Preserve history"
209 ignore)
210 (function :tag "Custom function")))
211
185(defcustom eww-after-render-hook nil 212(defcustom eww-after-render-hook nil
186 "A hook called after eww has finished rendering the buffer." 213 "A hook called after eww has finished rendering the buffer."
187 :version "25.1" 214 :version "25.1"
@@ -312,7 +339,10 @@ parameter, and should return the (possibly) transformed URL."
312 339
313(defvar eww-data nil) 340(defvar eww-data nil)
314(defvar eww-history nil) 341(defvar eww-history nil)
315(defvar eww-history-position 0) 342(defvar eww-history-position 0
343 "The 1-indexed position in `eww-history'.
344If zero, EWW is at the newest page, which isn't yet present in
345`eww-history'.")
316(defvar eww-prompt-history nil) 346(defvar eww-prompt-history nil)
317 347
318(defvar eww-local-regex "localhost" 348(defvar eww-local-regex "localhost"
@@ -402,6 +432,7 @@ For more information, see Info node `(eww) Top'."
402 (t 432 (t
403 (get-buffer-create "*eww*")))) 433 (get-buffer-create "*eww*"))))
404 (eww-setup-buffer) 434 (eww-setup-buffer)
435 (eww--before-browse)
405 ;; Check whether the domain only uses "Highly Restricted" Unicode 436 ;; Check whether the domain only uses "Highly Restricted" Unicode
406 ;; IDNA characters. If not, transform to punycode to indicate that 437 ;; IDNA characters. If not, transform to punycode to indicate that
407 ;; there may be funny business going on. 438 ;; there may be funny business going on.
@@ -654,7 +685,6 @@ The renaming scheme is performed in accordance with
654 (with-current-buffer buffer 685 (with-current-buffer buffer
655 (plist-put eww-data :url url) 686 (plist-put eww-data :url url)
656 (eww--after-page-change) 687 (eww--after-page-change)
657 (setq eww-history-position 0)
658 (and last-coding-system-used 688 (and last-coding-system-used
659 (set-buffer-file-coding-system last-coding-system-used)) 689 (set-buffer-file-coding-system last-coding-system-used))
660 (unless shr-fill-text 690 (unless shr-fill-text
@@ -905,6 +935,11 @@ The renaming scheme is performed in accordance with
905 `((?u . ,(or url "")) 935 `((?u . ,(or url ""))
906 (?t . ,title)))))))) 936 (?t . ,title))))))))
907 937
938(defun eww--before-browse ()
939 (funcall eww-before-browse-history-function)
940 (setq eww-history-position 0
941 eww-data (list :title "")))
942
908(defun eww--after-page-change () 943(defun eww--after-page-change ()
909 (eww-update-header-line-format) 944 (eww-update-header-line-format)
910 (eww--rename-buffer)) 945 (eww--rename-buffer))
@@ -1037,6 +1072,7 @@ the like."
1037 (base (plist-get eww-data :url))) 1072 (base (plist-get eww-data :url)))
1038 (eww-score-readability dom) 1073 (eww-score-readability dom)
1039 (eww-save-history) 1074 (eww-save-history)
1075 (eww--before-browse)
1040 (eww-display-html nil nil 1076 (eww-display-html nil nil
1041 (list 'base (list (cons 'href base)) 1077 (list 'base (list (cons 'href base))
1042 (eww-highest-readability dom)) 1078 (eww-highest-readability dom))
@@ -1129,9 +1165,9 @@ the like."
1129 ["Reload" eww-reload t] 1165 ["Reload" eww-reload t]
1130 ["Follow URL in new buffer" eww-open-in-new-buffer] 1166 ["Follow URL in new buffer" eww-open-in-new-buffer]
1131 ["Back to previous page" eww-back-url 1167 ["Back to previous page" eww-back-url
1132 :active (not (zerop (length eww-history)))] 1168 :active (< eww-history-position (length eww-history))]
1133 ["Forward to next page" eww-forward-url 1169 ["Forward to next page" eww-forward-url
1134 :active (not (zerop eww-history-position))] 1170 :active (> eww-history-position 1)]
1135 ["Browse with external browser" eww-browse-with-external-browser t] 1171 ["Browse with external browser" eww-browse-with-external-browser t]
1136 ["Download" eww-download t] 1172 ["Download" eww-download t]
1137 ["View page source" eww-view-source] 1173 ["View page source" eww-view-source]
@@ -1155,9 +1191,9 @@ the like."
1155 (easy-menu-define nil easy-menu nil 1191 (easy-menu-define nil easy-menu nil
1156 '("Eww" 1192 '("Eww"
1157 ["Back to previous page" eww-back-url 1193 ["Back to previous page" eww-back-url
1158 :visible (not (zerop (length eww-history)))] 1194 :active (< eww-history-position (length eww-history))]
1159 ["Forward to next page" eww-forward-url 1195 ["Forward to next page" eww-forward-url
1160 :visible (not (zerop eww-history-position))] 1196 :active (> eww-history-position 1)]
1161 ["Reload" eww-reload t])) 1197 ["Reload" eww-reload t]))
1162 (dolist (item (reverse (lookup-key easy-menu [menu-bar eww]))) 1198 (dolist (item (reverse (lookup-key easy-menu [menu-bar eww])))
1163 (when (consp item) 1199 (when (consp item)
@@ -1280,16 +1316,20 @@ instead of `browse-url-new-window-flag'."
1280 (interactive nil eww-mode) 1316 (interactive nil eww-mode)
1281 (when (>= eww-history-position (length eww-history)) 1317 (when (>= eww-history-position (length eww-history))
1282 (user-error "No previous page")) 1318 (user-error "No previous page"))
1283 (eww-save-history) 1319 (if (eww-save-history)
1284 (setq eww-history-position (+ eww-history-position 2)) 1320 ;; We were at the latest page (which was just added to the
1321 ;; history), so go back two entries.
1322 (setq eww-history-position 2)
1323 (setq eww-history-position (1+ eww-history-position)))
1285 (eww-restore-history (elt eww-history (1- eww-history-position)))) 1324 (eww-restore-history (elt eww-history (1- eww-history-position))))
1286 1325
1287(defun eww-forward-url () 1326(defun eww-forward-url ()
1288 "Go to the next displayed page." 1327 "Go to the next displayed page."
1289 (interactive nil eww-mode) 1328 (interactive nil eww-mode)
1290 (when (zerop eww-history-position) 1329 (when (<= eww-history-position 1)
1291 (user-error "No next page")) 1330 (user-error "No next page"))
1292 (eww-save-history) 1331 (eww-save-history)
1332 (setq eww-history-position (1- eww-history-position))
1293 (eww-restore-history (elt eww-history (1- eww-history-position)))) 1333 (eww-restore-history (elt eww-history (1- eww-history-position))))
1294 1334
1295(defun eww-restore-history (elem) 1335(defun eww-restore-history (elem)
@@ -1959,6 +1999,7 @@ If EXTERNAL is double prefix, browse in new buffer."
1959 (eww-same-page-p url (plist-get eww-data :url))) 1999 (eww-same-page-p url (plist-get eww-data :url)))
1960 (let ((point (point))) 2000 (let ((point (point)))
1961 (eww-save-history) 2001 (eww-save-history)
2002 (eww--before-browse)
1962 (plist-put eww-data :url url) 2003 (plist-put eww-data :url url)
1963 (goto-char (point-min)) 2004 (goto-char (point-min))
1964 (if-let ((match (text-property-search-forward 'shr-target-id target #'member))) 2005 (if-let ((match (text-property-search-forward 'shr-target-id target #'member)))
@@ -2289,11 +2330,69 @@ If ERROR-OUT, signal user-error if there are no bookmarks."
2289;;; History code 2330;;; History code
2290 2331
2291(defun eww-save-history () 2332(defun eww-save-history ()
2333 "Save the current page's data to the history.
2334If the current page is a historial one loaded from
2335`eww-history' (e.g. by calling `eww-back-url'), this will update the
2336page's entry in `eww-history' and return nil. Otherwise, add a new
2337entry to `eww-history' and return t."
2292 (plist-put eww-data :point (point)) 2338 (plist-put eww-data :point (point))
2293 (plist-put eww-data :text (buffer-string)) 2339 (plist-put eww-data :text (buffer-string))
2294 (let ((history-delete-duplicates nil)) 2340 (if (zerop eww-history-position)
2295 (add-to-history 'eww-history eww-data eww-history-limit t)) 2341 (let ((history-delete-duplicates nil))
2296 (setq eww-data (list :title ""))) 2342 (add-to-history 'eww-history eww-data eww-history-limit t)
2343 (setq eww-history-position 1)
2344 t)
2345 (setf (elt eww-history (1- eww-history-position)) eww-data)
2346 nil))
2347
2348(defun eww-delete-future-history ()
2349 "Remove any entries in `eww-history' after the currently-shown one.
2350This is useful for `eww-before-browse-history-function' to make EWW's
2351navigation to a new page from a historical one work like other web
2352browsers: it will delete any \"future\" history elements before adding
2353the new page to the end of the history.
2354
2355For example, if `eww-history' looks like this (going from newest to
2356oldest, with \"*\" marking the current page):
2357
2358 E D C* B A
2359
2360then calling this function updates `eww-history' to:
2361
2362 C* B A"
2363 (when (> eww-history-position 1)
2364 (setq eww-history (nthcdr (1- eww-history-position) eww-history)
2365 ;; We don't really need to set this since `eww--before-browse'
2366 ;; sets it too, but this ensures that other callers can use
2367 ;; this function and get the expected results.
2368 eww-history-position 1)))
2369
2370(defun eww-clone-previous-history ()
2371 "Clone and prepend entries in `eww-history' up to the currently-shown one.
2372These cloned entries get added to the beginning of `eww-history' so that
2373it's possible to navigate back to the very first page for this EWW
2374without deleting any history entries.
2375
2376For example, if `eww-history' looks like this (going from newest to
2377oldest, with \"*\" marking the current page):
2378
2379 E D C* B A
2380
2381then calling this function updates `eww-history' to:
2382
2383 C* B A E D C B A
2384
2385This is useful for setting `eww-before-browse-history-function' (which
2386see)."
2387 (when (> eww-history-position 1)
2388 (setq eww-history (take eww-history-limit
2389 (append (nthcdr (1- eww-history-position)
2390 eww-history)
2391 eww-history))
2392 ;; As with `eww-delete-future-history', we don't really need
2393 ;; to set this since `eww--before-browse' sets it too, but
2394 ;; let's be thorough.
2395 eww-history-position 1)))
2297 2396
2298(defvar eww-current-buffer) 2397(defvar eww-current-buffer)
2299 2398
diff --git a/test/lisp/net/eww-tests.el b/test/lisp/net/eww-tests.el
new file mode 100644
index 00000000000..ced84322e3a
--- /dev/null
+++ b/test/lisp/net/eww-tests.el
@@ -0,0 +1,179 @@
1;;; eww-tests.el --- tests for eww.el -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2024 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;;; Code:
23
24(require 'ert)
25(require 'eww)
26
27(defvar eww-test--response-function (lambda (url) (concat "\n" url))
28 "A function for returning a mock response for URL.
29The default just returns an empty list of headers URL as the body.")
30
31(defmacro eww-test--with-mock-retrieve (&rest body)
32 "Evaluate BODY with a mock implementation of `eww-retrieve'.
33This avoids network requests during our tests. Additionally, prepare a
34temporary EWW buffer for our tests."
35 (declare (indent 1))
36 `(cl-letf (((symbol-function 'eww-retrieve)
37 (lambda (url callback args)
38 (with-temp-buffer
39 (insert (funcall eww-test--response-function url))
40 (apply callback nil args)))))
41 (with-temp-buffer
42 (eww-mode)
43 ,@body)))
44
45(defun eww-test--history-urls ()
46 (mapcar (lambda (elem) (plist-get elem :url)) eww-history))
47
48;;; Tests:
49
50(ert-deftest eww-test/history/new-page ()
51 "Test that when visiting a new page, the previous one goes into the history."
52 (eww-test--with-mock-retrieve
53 (eww "one.invalid")
54 (eww "two.invalid")
55 (should (equal (eww-test--history-urls)
56 '("http://one.invalid/")))
57 (eww "three.invalid")
58 (should (equal (eww-test--history-urls)
59 '("http://two.invalid/"
60 "http://one.invalid/")))))
61
62(ert-deftest eww-test/history/back-forward ()
63 "Test that navigating through history just changes our history position.
64See bug#69232."
65 (eww-test--with-mock-retrieve
66 (eww "one.invalid")
67 (eww "two.invalid")
68 (eww "three.invalid")
69 (let ((url-history '("http://three.invalid/"
70 "http://two.invalid/"
71 "http://one.invalid/")))
72 ;; Go back one page. This should add "three.invalid" to the
73 ;; history, making our position in the list 2.
74 (eww-back-url)
75 (should (equal (eww-test--history-urls) url-history))
76 (should (= eww-history-position 2))
77 ;; Go back again.
78 (eww-back-url)
79 (should (equal (eww-test--history-urls) url-history))
80 (should (= eww-history-position 3))
81 ;; At the beginning of the history, so trying to go back should
82 ;; signal an error.
83 (should-error (eww-back-url))
84 ;; Go forward once.
85 (eww-forward-url)
86 (should (equal (eww-test--history-urls) url-history))
87 (should (= eww-history-position 2))
88 ;; Go forward again.
89 (eww-forward-url)
90 (should (equal (eww-test--history-urls) url-history))
91 (should (= eww-history-position 1))
92 ;; At the end of the history, so trying to go forward should
93 ;; signal an error.
94 (should-error (eww-forward-url)))))
95
96(ert-deftest eww-test/history/reload-in-place ()
97 "Test that reloading historical pages updates their history entry in-place.
98See bug#69232."
99 (eww-test--with-mock-retrieve
100 (eww "one.invalid")
101 (eww "two.invalid")
102 (eww "three.invalid")
103 (eww-back-url)
104 ;; Make sure our history has the original page text.
105 (should (equal (plist-get (nth 1 eww-history) :text)
106 "http://two.invalid/"))
107 (should (= eww-history-position 2))
108 ;; Reload the page.
109 (let ((eww-test--response-function
110 (lambda (url) (concat "\nreloaded " url))))
111 (eww-reload)
112 (should (= eww-history-position 2)))
113 ;; Go to another page, and make sure the history is correct,
114 ;; including the reloaded page text.
115 (eww "four.invalid")
116 (should (equal (eww-test--history-urls) '("http://two.invalid/"
117 "http://one.invalid/")))
118 (should (equal (plist-get (nth 0 eww-history) :text)
119 "reloaded http://two.invalid/"))
120 (should (= eww-history-position 0))))
121
122(ert-deftest eww-test/history/before-navigate/delete-future-history ()
123 "Test that going to a new page from a historical one deletes future history.
124See bug#69232."
125 (eww-test--with-mock-retrieve
126 (eww "one.invalid")
127 (eww "two.invalid")
128 (eww "three.invalid")
129 (eww-back-url)
130 (eww "four.invalid")
131 (eww "five.invalid")
132 (should (equal (eww-test--history-urls) '("http://four.invalid/"
133 "http://two.invalid/"
134 "http://one.invalid/")))
135 (should (= eww-history-position 0))))
136
137(ert-deftest eww-test/history/before-navigate/ignore-history ()
138 "Test that going to a new page from a historical one preserves history.
139This sets `eww-before-browse-history-function' to `ignore' to preserve
140history. See bug#69232."
141 (let ((eww-before-browse-history-function #'ignore))
142 (eww-test--with-mock-retrieve
143 (eww "one.invalid")
144 (eww "two.invalid")
145 (eww "three.invalid")
146 (eww-back-url)
147 (eww "four.invalid")
148 (eww "five.invalid")
149 (should (equal (eww-test--history-urls) '("http://four.invalid/"
150 "http://three.invalid/"
151 "http://two.invalid/"
152 "http://one.invalid/")))
153 (should (= eww-history-position 0)))))
154
155(ert-deftest eww-test/history/before-navigate/clone-previous ()
156 "Test that going to a new page from a historical one clones prior history.
157This sets `eww-before-browse-history-function' to
158`eww-clone-previous-history' to clone the history. See bug#69232."
159 (let ((eww-before-browse-history-function #'eww-clone-previous-history))
160 (eww-test--with-mock-retrieve
161 (eww "one.invalid")
162 (eww "two.invalid")
163 (eww "three.invalid")
164 (eww-back-url)
165 (eww "four.invalid")
166 (eww "five.invalid")
167 (should (equal (eww-test--history-urls)
168 '(;; New page and cloned history.
169 "http://four.invalid/"
170 "http://two.invalid/"
171 "http://one.invalid/"
172 ;; Original history.
173 "http://three.invalid/"
174 "http://two.invalid/"
175 "http://one.invalid/")))
176 (should (= eww-history-position 0)))))
177
178(provide 'eww-tests)
179;; eww-tests.el ends here