Subject: bug#76275: 31.0.50; frame-inhibit-implied-resize broken on recent master

From: martin rudalics <[email-masked]>
Date: Sat, 15 Feb 2025 11:34:47 +0100

>> (1) Is the
 >>
 >>> (defun spw/scale-default (frame)
 >>>     (set-face-attribute 'default frame :height 120))
 >>> (add-to-list 'window-size-change-functions #'spw/scale-default)
 >>
 >> needed to reproduce the problem and how or why?
 >
 > Yes, it's needed.  I obtained it by bisecting my init.el to find a
 > minimal reproducer, so I don't know exactly why it's needed.  But I
 > think it's because changing the face size triggers the kind of implied
 > resize that frame-inhibit-implied-resize is meant to prevent.

So the sequence of events is apparently

- You are in an initial phase where 'frame-inhibit-implied-resize'
   non-nil has no effect because the tool bar was not yet resized.

- You trigger a frame size change via 'set-face-attribute' which
   apparently "gets through", that is, resizes the frame which is
   something that should not happen.

- When the tool bar gets finally resized it's too late.  The last
   change can be no more undone.

 >> If so, then please tell us (via GDB) when and how these bypass the
 >>
 >> 	  || !f->can_set_window_size
 >> 	  || !f->after_make_frame
 >>
 >> check in run_window_change_functions in window.c.
 >
 > My breakpoint was hit three times.  In each case, both
 > f->can_set_window_size and f->after_make_frame were true.
 >
 > The backtrace in all three cases is the same, in that the code is called
 > via redisplay.
 >
 >> If they do bypass it, could you insert an additional
 >>
 >>            || !f->tool_bar_resized
 >>
 >> there and tell us what happens then?
 >
 > Each of the three times my breakpoint is hit, f->tool_bar_resized is
 > false.

This and your later ....

 > Oh, and: inserting this line does not fix the bug.

... are somewhat contradictory.  It means that we should be able to
restore the old behavior with the disjunct added because then we are not
allowed to run the hook.  Are you sure you did something like

       if (!FRAME_LIVE_P (f)
	  || !f->can_set_window_size
	  || !f->after_make_frame
	  || !f->tool_bar_resized
	  || FRAME_TOOLTIP_P (f)
	  || !(frame_window_change
	       || frame_selected_change
	       || frame_selected_window_change
	       || frame_window_state_change))
	/* Either we are not allowed to run hooks for this frame or no
	   window change has been reported for it since the last time
	   we ran window change functions on it.  */
	continue;

If that hook is still run, we'd have to find out why.

 > I have also observed something else from this debugging.  Switching
 > focus between GUD (in my main Emacs session) and the incorrectly sized
 > frame can cause the incorrectly sized frame to jump back to the correct
 > size.  When that occurs, my breakpoint is not hit.

Since you are apparently running the hook anyway, we could try to do the
debugging within a function run by the hook.  We can also try using
'frame--size-history' to find out who is responsible.  In either case we
have to find out what allows 'set-face-attribute' to run so early.

To clarify: It's one thing that inhibiting implied resizing is not done
in your scenario because 'frame_inhibit_resize' insists on the tool bar
being fully drawn.  The one thing that I want to find out first why in
the case at hand the hook is run although the tool bar has not been
fully drawn yet.

 >> (2) Is the
 >>
 >>>       (unwind-protect
 >>> 	(with-selected-frame frame
 >>> 	  (set-window-scroll-bars (minibuffer-window frame) 0 nil 0 nil t)
 >>> 	  (apply #'completing-read prompt collection optional-args))
 >>>         (delete-frame frame))))
 >>
 >> needed to reproduce the problem and how or why?
 >
 > It's needed in some form, yes, but you prompted me to investigate
 > further and this is my new minimal reproducer:
 >
 > sway-completing-read.el:
 > --8<---------------cut here---------------start------------->8---
 > (defun spw/scale-default (frame)
 >    (set-face-attribute 'default frame :height 120))
 > (add-to-list 'window-size-change-functions #'spw/scale-default)
 >
 > (defun spw/sway-completing-read (prompt collection &rest optional-args)
 >    (let* ((frame-inhibit-implied-resize t)
 > 	 (frame (make-frame `((title . "Emacs Sway input")
 > 			      (minibuffer . only)
 > 			      (display . ,(getenv "WAYLAND_DISPLAY"))))))
 >      (with-selected-frame frame
 >        (apply #'completing-read prompt collection optional-args))))
 > --8<---------------cut here---------------end--------------->8---
 >
 > So, the call to set-window-scroll-bars is not essential, but putting
 > some text in the frame would seem to be essential.

I am also stupefied by the fact that the

 >      (with-selected-frame frame
 >        (apply #'completing-read prompt collection optional-args))))

is apparently needed.  WOW a pure 'make-frame' together with the hook on
'window-size-change-functions' is _not_ sufficient.  Right?  Is the

(apply #'completing-read prompt collection optional-args))))

needed literally or would just selecting the frame suffice?  Or does
‘completing-read’ trigger the window size change that puts the bad
sequence in motion?

One other thing: Would enlarging the scope via

(defun spw/scale-default (frame)
   (let ((frame-inhibit-implied-resize t))
     (set-face-attribute 'default frame :height 120)))

change anything?

When worse comes to worst, we can always make the offending commit
optional but I'd rather like to find out what happens first.

martin