Subject: bug#77299: eww-auto-rename-buffer 'title interaction with eww-readable-urls

From: Jim Porter <[email-masked]>
Date: Thu, 19 Jun 2025 12:27:53 -0700

On 6/18/2025 4:31 AM, Eli Zaretskii wrote:
> Please show the patch so I could decide whether such a change is okay
> for emacs-30.

How about these patches? One for the master branch, and one for Emacs 30.

From 519eb14a64e3a9209911130a291b276d9f6f961f Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Thu, 19 Jun 2025 12:23:24 -0700
Subject: [PATCH] Populate the ':title' in EWW when defaulting to readable mode

Do not merge to master.

* lisp/net/eww.el (eww-display-document): When 'eww-auto-rename-buffer'
is set to 'title', always render the full document first to pupoulate
':title' (bug#77299).
---
 lisp/net/eww.el | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index c3e927b74a6..b83672b73d9 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -828,6 +828,13 @@ eww-display-html
   (unless document
     (let ((dom (eww--parse-html-region (point) (point-max) charset)))
       (when (eww-default-readable-p url)
+        (when (eq eww-auto-rename-buffer 'title)
+          ;; HACK: Work around bug#77299.  By displaying the full
+          ;; document first, we can ensure that the `:title' property in
+          ;; `eww-data' gets set properly.  This is inefficient, since
+          ;; it requires rendering the document twice, and might load
+          ;; images that would otherwise be excluded from readable mode.
+          (eww-display-document (eww-document-base url dom) point buffer))
         (eww-score-readability dom)
         (setq dom (eww-highest-readability dom))
         (with-current-buffer buffer
-- 
2.25.1

From 586645d27ac3a1bc2b5882743f9ba3fcea4de3cb Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Tue, 17 Jun 2025 09:11:55 -0700
Subject: [PATCH] When making a readable page in EWW, include the <title> and
 similar tags

* lisp/net/eww.el (eww--walk-readability, eww-readable-dom): New
functions.
(eww-display-html): Call 'eww-readable-dom'.
(eww-readable): Call 'eww-readable-dom'.  Don't copy over 'eww-data'
properties that our new readable page can handle on its own.
(eww-score-readability): Rewrite in terms of 'eww--walk-readability'.
Make obsolete.
(eww-highest-readability): Make obsolete.
---
 lisp/net/eww.el | 95 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 69 insertions(+), 26 deletions(-)

diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index 5462b3c78f4..458f0ddcc48 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -863,8 +863,7 @@ eww-display-html
   (unless document
     (let ((dom (eww--parse-html-region (point) (point-max) charset)))
       (when (eww-default-readable-p url)
-        (eww-score-readability dom)
-        (setq dom (eww-highest-readability dom))
+        (setq dom (eww-readable-dom dom))
         (with-current-buffer buffer
           (plist-put eww-data :readable t)))
       (setq document (eww-document-base url dom))))
@@ -1163,42 +1162,86 @@ eww-readable
                 (eww--parse-html-region (point-min) (point-max))))
          (base (plist-get eww-data :url)))
     (when make-readable
-      (eww-score-readability dom)
-      (setq dom (eww-highest-readability dom)))
+      (setq dom (eww-readable-dom dom)))
     (when eww-readable-adds-to-history
       (eww-save-history)
       (eww--before-browse)
-      (dolist (elem '(:source :url :title :next :previous :up :peer))
+      (dolist (elem '(:source :url :peer))
         (plist-put eww-data elem (plist-get old-data elem))))
     (eww-display-document (eww-document-base base dom))
     (plist-put eww-data :readable make-readable)
     (eww--after-page-change)))
 
-(defun eww-score-readability (node)
-  (let ((score -1))
-    (cond
-     ((memq (dom-tag node) '(script head comment))
-      (setq score -2))
-     ((eq (dom-tag node) 'meta)
-      (setq score -1))
-     ((eq (dom-tag node) 'img)
-      (setq score 2))
-     ((eq (dom-tag node) 'a)
-      (setq score (- (length (split-string (dom-text node))))))
-     (t
+(defun eww--walk-readability (node callback &optional noscore)
+  "Walk through all children of NODE to score readability.
+After scoring, call CALLBACK with the node and score.  If NOSCORE is
+non-nil, don't actually compute a score; just call the callback."
+  (let ((score nil))
+    (unless noscore
+      (cond
+       ((stringp node)
+        (setq score (length (split-string node))
+              noscore t))
+       ((memq (dom-tag node) '(script head comment))
+        (setq score -2
+              noscore t))
+       ((eq (dom-tag node) 'meta)
+        (setq score -1
+              noscore t))
+       ((eq (dom-tag node) 'img)
+        (setq score 2
+              noscore t))
+       ((eq (dom-tag node) 'a)
+        (setq score (- (length (split-string (dom-text node))))
+              noscore t))
+       (t
+        (setq score -1))))
+    (when (consp node)
       (dolist (elem (dom-children node))
-	(cond
-         ((stringp elem)
-          (setq score (+ score (length (split-string elem)))))
-         ((consp elem)
-	  (setq score (+ score
-			 (or (cdr (assoc :eww-readability-score (cdr elem)))
-			     (eww-score-readability elem)))))))))
-    ;; Cache the score of the node to avoid recomputing all the time.
-    (dom-set-attribute node :eww-readability-score score)
+        (let ((subscore (eww--walk-readability elem callback noscore)))
+          (when (and (not noscore) subscore)
+            (incf score subscore)))))
+    (funcall callback node score)
     score))
 
+(defun eww-readable-dom (dom)
+  "Return a readable version of DOM."
+  (let ((head-nodes nil)
+        (best-node nil)
+        (best-score most-negative-fixnum))
+    (eww--walk-readability
+     dom
+     (lambda (node score)
+       (when (consp node)
+         (when (and score (> score best-score)
+                    ;; We set a lower bound to how long we accept that
+                    ;; the readable portion of the page is going to be.
+                    (> (length (split-string (dom-texts node))) 100))
+           (setq best-score score
+                 best-node node))
+         ;; FIXME: We should probably filter out stylesheet <link> tags.
+         ;; Not that it really matters since we don't *do* anything with
+         ;; stylesheets...
+         (when (memq (dom-tag node) '(title link))
+           ;; FIXME: Uniquify the DOM so we don't include a node and its
+           ;; children separately?
+           (push node head-nodes)))))
+    (if best-node
+        `(html nil
+             (head nil ,@head-nodes)
+             (body nil ,best-node))
+      dom)))
+
+(defun eww-score-readability (node)
+  (declare (obsolete 'eww--walk-readability "31.1"))
+  (eww--walk-readability
+   node
+   (lambda (node score)
+     (when (and score (consp node))
+       (dom-set-attribute node :eww-readability-score score)))))
+
 (defun eww-highest-readability (node)
+  (declare (obsolete 'eww-readable-dom "31.1"))
   (let ((result node)
 	highest)
     (dolist (elem (dom-non-text-children node))
-- 
2.25.1