Subject: 29.4; Inconsistent parameter types sent to GUI selection converters

From: Derek Upham <[email-masked]>
Date: Mon, 02 Sep 2024 11:15:40 -0700

Existing code
-------------

We'll have to go into some obscure areas of the GUI selection 
code.
Let's start with xselect-convert-to-targets (select.el).

  (defun xselect-convert-to-targets (selection _type value)
    ;; Return a vector of atoms, but remove duplicates first.
    (if (eq selection 'XdndSelection)
        ;; This isn't required by the XDND protocol, and sure 
        enough no
        ;; clients seem to dependent on it, but Emacs implements 
        the
        ;; receiver side of the Motif drop protocol by looking at 
        the
        ;; initiator selection's TARGETS target (which Motif 
        provides)
        ;; instead of the target table on the drag window, so it 
        seems
        ;; plausible for other clients to rely on that as well.
        (apply #'vector (mapcar #'intern x-dnd-targets-list))
      (apply #'vector
             (delete-dups
              `( TIMESTAMP MULTIPLENIL
                 . ,(delq '_EMACS_INTERNAL
                          (mapcar (lambda (conv)
                                    (if (or (not (consp (cdr 
                                    conv)))
                                            (funcall (cadr conv) 
                                            selection
                                                     (car conv) 
                                                     value))
                                        (car conv)
                                      '_EMACS_INTERNAL))
                                  selection-converter-alist)))))))

This function evaluates each converter in 
selection-converter-alist
against the selection value, and returns the labels of any 
converters
that return non-NIL.  The goal here is to filter out targets that 
Emacs
can't vend for the current value.  The converters are responsible 
for
noticing and rejecting inputs that they can't support.

Be aware that the "value" parameter may be a string with text
properties.  The "gui-set-selection" Info documentation mentions 
this:

     If DATA is a string, then its text properties can specify 
     values
     used for individual data types.  For example, if DATA has a
     property named ‘text/uri-list’, then a call to 
     ‘gui-get-selection’
     with the data type ‘text/uri-list’ will result in the value 
     of that
     property being used instead of DATA itself.

Now compare the xselect-convert-to-targets function with the code 
in
x_get_local_selection (xselect.c, excerpted).

      CHECK_SYMBOL (target_type);
      handler_fn = CDR (Fassq (target_type, 
      Vselection_converter_alist));

      if (CONSP (handler_fn))
	handler_fn = XCDR (handler_fn);

      if (!need_alternate)
	tem = XCAR (XCDR (local_value));
      else
	tem = XCAR (XCDR (XCDR (XCDR (XCDR (local_value)))));

      if (STRINGP (tem))
	{
	  local_value = Fget_text_property (make_fixnum (0),
					    target_type, tem);

	  if (!NILP (local_value))
	    tem = local_value;
	}

      if (!NILP (handler_fn))
	value = call3 (handler_fn, selection_symbol,
		       ((local_request
			 && NILP 
			 (Vx_treat_local_requests_remotely))
			? Qnil
			: target_type),
		       tem);
      else
	value = Qnil;

The caller (possibly another X client) provides the target, which
defines the converter to use.  If tem is a string, then we check 
for a
property that matches the target type.  If such a property exists, 
we
clobber the existing string with the associated property's object. 
Then
we call the converter.

Problem
-------

This discrepancy trips up potential HTML support.

A typical application like Firefox or LibreOffice vends both 
text/html
and text/plain content.  Clients will ask for the targets, then 
ask
for the text/html value if available, falling back to text/plain. 
For
example, we might want to support an italiced "foo", while falling
back to the underlying word.

  #("foo" 0 3 (text/html "<i>foo</i>"))

We want to advertise a text/html target only when our value has a
text/html property.  We can do that with new 
"xselect-convert-to-html"
function in selection-converter-alist.

  (text/html . xselect-convert-to-html)

The function returns true if the input is a string with a 
text/html
property.  But if the client then *asks* for the text/html, the C 
code
will send the same function a plain string “<i>foo</i>” without 
the
property.  The function bails out with NIL.  Most clients will 
then fall
back and ask for the text/plain target.

In broad terms, we can’t distinguish between regular text and HTML 
text
from first principles.  We need guidance from upstream.  Also note 
that
if we write the HTML converter function such that it doesn’t test 
for
and require that text/html property, then Emacs will happily vend 
the
plain text strings to text/html requesters.

Possible fixes
--------------

The current implementation doesn't nail down the protocol and the 
data types.

There are a couple of potential fixes; some are more invasive than 
others.

1. We can define that, if we have a string, then the string is 
always
   implicitly a variant type that we pass the converters.  Just 
   take out
   the local_value clobbering in the C code.  The HTML converter 
   and all
   other converters can then consistently look for and extract 
   their
   relevant property from the string.  This is a breaking behavior
   change, but for already-broken behavior.  And the built-in 
   converters
   in select.el don’t seem to care about those properties.

2. We can put the properties back in.  Once we extract the 
property
   string local_value, copy the properties of the original string 
   tem
   into local_value.  Then overwrite tem.  The rule for handlers 
   is then
   to *look* for the property, but it can use property’s string or 
   the
   underlying string.

3. We can declare that callers have to add type tags to the 
property objects.

     #("foo" 0 3 (text/html (html . "<i>foo</i>")))

   Then the converters are responsible for receiving that string 
   *or*
   (html . "<i>foo</i>"), depending on which function calls them,
   handling both inputs.  This is ugly, but it works for a 
   prototype
   HTML converter on top of the existing v29.4 code.

-- 
Derek Upham
[email-masked]