Closed Bug 938991 Opened 11 years ago Closed 9 years ago

text/rtf support for clipboard data

Categories

(Core :: DOM: Serializers, defect)

25 Branch
x86
macOS
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla45
Tracking Status
firefox45 --- fixed

People

(Reporter: andrew.herron, Assigned: jorgk-bmo)

References

Details

(Whiteboard: dupeme?)

Attachments

(4 files, 10 obsolete files)

15.33 KB, application/zip
Details
68.96 KB, image/png
Details
1.07 KB, text/html
Details
12.29 KB, patch
enndeakin
: review+
Details | Diff | Splinter Review
Attached file replication case
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36

Steps to reproduce:

1. Open rtfpaste.html
2. Copy RTF data (a simple word doc is provided, on OS X copying content from Safari also works)
3. Paste into the black box (a contenteditable div)


Actual results:

Nothing is printed to the console. The page includes a script that checks for RTF data in the clipboardData paste event object and logs any it finds to the console.


Expected results:

A print of RTF data to the console.

We would like to have access to the text/rtf clipboard data. It includes information that is not available in the text/html format.

Safari/chrome both claim to make this data available, although getData() returns an empty string. We have logged bugs with both browsers.
Confirming, works in Chrome.
Status: UNCONFIRMED → NEW
Component: Untriaged → Editor
Ever confirmed: true
Product: Firefox → Core
Dupe of bug 79864?
Whiteboard: dupeme?
It's similar, but that's about translating between RTF and HTML for copy/paste operations. I want to have access to the raw RTF data.

Also RTF is available on more platforms than just OS X (in the MS Word case Windows is another requirement) but I didn't see a way to specify that in the bug details.
Component: Editor → Serializers
The first step for this is bug 906420 which would give us DataTransfer.items.

Once that bug lands, fixing this in theory should be as simple as updating the list of known mime types in DataTransfer::CacheExternalClipboardFormats() to include text/rtf.  I think that with that, you should be able to get access to the raw RTF data in a web app by handling the paste event and looking for text/rtf data in clipboardData.items.
Depends on: 906420
See Also: → 860857
You'd also likely need to update the platform-specfic widget code to handle rtf.

I'm not clear why 906420 is needed.
(In reply to Neil Deakin from comment #5)
> You'd also likely need to update the platform-specfic widget code to handle
> rtf.

Do we need to do that to access the raw RTF data?

> I'm not clear why 906420 is needed.

As a way to expose the data to the web page using the standard API.
> Do we need to do that to access the raw RTF data?
> 

It might be available as plain text, but I'm not certain. On Mac on Windows at least there is a different clipboard type for rtf I believe.
 
> As a way to expose the data to the web page using the standard API.

Why is getData("text/rtf") not sufficient? The .items api added in 906420 doesn't provide any additional way to provide data apart from supporting multiple items.
The .items api wouldn't be required to solve the attached test case, it searches .types for "text/rtf" and then uses getData() as Neil suggests.

This is how we access "text/html" data at the moment.
OK then getData() is all you need!
Andrew, can you please clarify the issue here:
Comment #0 says: Expected results: A print of RTF data to the console.

Here is what I tried on Windows. Since I don't have MS Office, I started "Wordpad" and created rich text (red/black).

Pasting this into Chrome, I get two types: text/plain and text/rtf. The text is pasted as plain. getData() doesn't seem to work for the rtf part (as stated in comment #0). So we expect pasting as plain, no conversion to HTML should take place?

Pasting this into Firefox, I get one one type: text/plain.

So the problem is the missing notification, not the fact that the pasting happens as plain text?
And then of course, if the notification worked, getData() should also return something useful.
Like what? I have no comparison.

BTW, IE10 pastes the rich text (red/black) but prints FAILURE!
We don't want conversion to HTML or paste as plain text. We want the actual, raw, RTF syntax. Using the example in IE falls back to native ContentEditable paste behaviour (IE doesn't implement HTML5 clipboard APIs), which is why you see the pasted content but it prints failure.

If you use a clipboard viewer (e.g. http://www.freeclipboardviewer.com/) you can see what we need. It shows "Rich Text Format" which is the rendered syntax, it also seems to have "RTF As Text" which will show what we expect to receive in JS.

Why we need this is where things get complicated. For now, I will just say that we use a custom Flash object to capture this data today (Flash does provide us access to RTF when pasting) and pass the raw RTF syntax back to JS. Once this is implemented, we will be able to remove our dependency on Flash which is a major and growing priority for us as the world moves towards the death of Flash.
Assignee: nobody → mozilla
I poked around a bit and got this to work on Windows. I don't have Mac hardware, so I can't do the Mac part. I'll submit the patch once I've cleaned it up.
No longer depends on: 906420
Added some JS to monitor drop events as well.
Attachment #8681833 - Attachment is obsolete: true
(copy/paste error) Make the drop listener report as "dropListener".
Attachment #8682944 - Attachment is obsolete: true
Attached patch WIP (v1) (obsolete) — Splinter Review
Work in progress.
The Windows part works, the Mac part doesn't.
Defines need to be created for "text/rtf".
Attachment #8682948 - Attachment description: Simple page that lets you paste or drop an displays the clipboard data on the console → Simple page that lets you paste or drop and displays the clipboard data on the console
Just for the record: RTF data is pure 7bit ASCII (https://en.wikipedia.org/wiki/Rich_Text_Format#Character_encoding). For example |Jörgßá안녕하세요| gives:
{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Calibri;}{\f1\fnil\fcharset129 Malgun Gothic;}}
\uc1\pard\nowidctlpar\lang9\f0\fs22 J\'f6rg\'df\'e1\lang1033\f1\fs24\'be\'c8\'b3\'e7\'c7\'cf\'bc\'bc\'bf\'e4}
Attached patch WIP (v2) (obsolete) — Splinter Review
Now working for Windows and Mac.

Andrew, can you please download test versions from here:
https://ftp-ssl.mozilla.org/pub/firefox/try-builds/mozilla@jorgk.com-4ccd8d91afff847ecc049d3e00bee2c5c51946d9/

To get it going on Mac I got a tip from our friends of Chromium:
https://src.chromium.org/svn/trunk/src/ui/base/clipboard/clipboard_mac.mm
Look for ReadRTF and ReadData.
Attachment #8683385 - Attachment is obsolete: true
Attached patch Proposed solution (v1) (obsolete) — Splinter Review
Slight change to the Mac paste processing to align it with the Mac drop processing.

Mac binary:
https://ftp-ssl.mozilla.org/pub/firefox/try-builds/mozilla@jorgk.com-3bea7736b1ee3b23cb0ddf1f47f5420e93a56cc4/try-macosx64/firefox-45.0a1.en-US.mac.dmg
Attachment #8684699 - Attachment is obsolete: true
Comment on attachment 8684775 [details] [diff] [review]
Proposed solution (v1)

I'd said this is ready for review or at least feedback, but sadly the bug reporter Andrew tried the supplied binaries and gets not text/rtf data returned.

I could attach screenshots for Windows and Mac (Yosemite) showing it working, so I don't know what's going on.

Meanwhile I sent this off to try, and I have a full green run. Oh boy, a lot of orange these days:
https://treeherder.mozilla.org/#/jobs?repo=try&revision=1ec43d613c3f

Here are some binaries, but as far as I know they should be the same since no code was changed since last time:
https://ftp-ssl.mozilla.org/pub/firefox/try-builds/mozilla@jorgk.com-1ec43d613c3f22e3b1ad3e4f542ff8d7d38a6b1e/
Attachment #8684775 - Flags: feedback?(enndeakin)
Jolly good, or bad, in fact, I found the problem.

On Windows, dragging from Wordpad always works, but copying only works when e10s (Enable multi-process Nightly) is switched off. I had done my testing on a profile with e10s switched off, for easier debugging, but on a new profile with e10s enabled by default, the copying doesn't work as Andrew said.

On Mac I only have Yosemite on a virtual machine. I checked and it's running with e10s turned off. I can't turn e10s on due to "hardware" limitations (obviously limitations of the emulation).

Anyway, looks like it's an e10s problem on both platforms. So Andrew, you can do your testing with e10s turned off while I'll look into it further.

Neil, can you give me some inspiration as to where to start looking?
Flags: needinfo?(enndeakin)
Here are a few bugs on e10s to do with copy and drag/drop:
Bug 1058712, bug 1071562, bug 936092. Looks like Neil is the right guy to ask.
Attached patch Proposed solution (v2) (obsolete) — Splinter Review
Found the missing bit that makes e10s work. It was in the clipboard proxy.
I will get new binaries ready in a few hours.
Attachment #8684775 - Attachment is obsolete: true
Attachment #8684775 - Flags: feedback?(enndeakin)
Flags: needinfo?(enndeakin)
Comment on attachment 8685066 [details] [diff] [review]
Proposed solution (v2)

The bug reporter Andrew did extensive testing and is satisfied, so this is ready for review.
Attachment #8685066 - Flags: review?(enndeakin)
Comment on attachment 8685066 [details] [diff] [review]
Proposed solution (v2)

>   // there isn't a way to get a list of the formats that might be available on
>   // all platforms, so just check for the types that can actually be imported
>   // XXXndeakin there are some other formats but those are platform specific.
>-  const char* formats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime, kUnicodeMime };
>+  const char* formats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime, kUnicodeMime, kRTFMime };

You want to put RTF just after HTML since it will generally be more specific than the text-only version. Same with the other place this is done.


>diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm
>   nsresult rv;
>   nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
>   if (NS_FAILED(rv))
>     return NO;
>   trans->Init(nullptr);
> 
>   trans->AddDataFlavor(kUnicodeMime);
>   trans->AddDataFlavor(kHTMLMime);
>+  trans->AddDataFlavor(kRTFMime);

This function is/was used for the Apple Services menu and we don't handle RTF for it, so no need to retrieve it here.
 

>-      NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
>+      NSData* stringData;
>+      if ([pboardType isEqualToString:NSRTFPboardType]) {
>+        stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
>+      } else {
>+        stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
>+      }

I know RTF should expect only 8-bit characters, but do we care enough to not convert it? Do Mac applications actually provide it in ASCII from the pasteboard?


>diff --git a/widget/nsITransferable.idl b/widget/nsITransferable.idl
>-[scriptable, uuid(97e0c418-1c1e-4106-bad1-9fcb11dff2fe)]
>+[scriptable, uuid(1f602950-ead4-4ab5-9a37-ca826b994726)]
> interface nsITransferable : nsISupports

You didn't change the interface so you shouldn't need to change the uuid.


>       const char * start = reinterpret_cast<const char*>(aDataBuff);
>+      // RTF on Windows is known to sometimes deliver an extra null byte.
>+      if (aDataLen > 0 && start[aDataLen - 1] == '\0')
>+        aDataLen--;
>       primitive->SetData(Substring(start, start + aDataLen));

I'll need to look at this some more.


>       }
>+      else if ( strcmp(flavorStr, kRTFMime) == 0 ) {
>+        FORMATETC rtfFE;
>+        // Add RTF
>+        SET_FORMATETC(rtfFE, ::RegisterClipboardFormat(L"Rich Text Format"), 0,
>+                                                       DVASPECT_CONTENT, -1,
>+                                                       TYMED_HGLOBAL)
>+        dObj->AddDataFlavor(flavorStr, &rtfFE);

The earlier call to GetFormat already called RegisterClipboardFormat, no?

I was on vacation so wasn't able to review this earlier. In the meantime, bug 966986 makes this patch not apply anymore, so I wasn't able to test myself. Can you provide an updated patch? I tried updating it myself, but got blank rtf data, or an error occuring.
Attachment #8685066 - Flags: review?(enndeakin) → review-
Thanks for the review, I will refresh this and address the problems you mentioned.
Meanwhile, perhaps you can review bug 586587. I applied this patch on top of the one from bug 586587.

As for the vacation: I hope it was relaxing, but it would have been more relaxing for the people waiting for review, especially for bug 586587 which seems to be getting close, to know about the unavailability ;-)
(In reply to Neil Deakin from comment #31)

> You want to put RTF just after HTML since it will generally be more specific
> than the text-only version. Same with the other place this is done.
OK.

> This function is/was used for the Apple Services menu and we don't handle
> RTF for it, so no need to retrieve it here.
OK, I'll remove the line.
 
> I know RTF should expect only 8-bit characters, but do we care enough to not
> convert it? Do Mac applications actually provide it in ASCII from the
> pasteboard?
My experiments showed that the RTF data only got returned when asking for
stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];

> You didn't change the interface so you shouldn't need to change the uuid.
OK.
 
> >       const char * start = reinterpret_cast<const char*>(aDataBuff);
> >+      // RTF on Windows is known to sometimes deliver an extra null byte.
> >+      if (aDataLen > 0 && start[aDataLen - 1] == '\0')
> >+        aDataLen--;
> >       primitive->SetData(Substring(start, start + aDataLen));
> I'll need to look at this some more.
Well, Windows returns extra Nulls at the end, for example if you paste "aaá" from Wordpad.
You can see such a Null in attachment 8681837 [details]. There is no harm an checking the last byte and
truncating it.

> >+      else if ( strcmp(flavorStr, kRTFMime) == 0 ) {
> >+        FORMATETC rtfFE;
> >+        // Add RTF
> >+        SET_FORMATETC(rtfFE, ::RegisterClipboardFormat(L"Rich Text Format"), 0,
> >+                                                       DVASPECT_CONTENT, -1,
> >+                                                       TYMED_HGLOBAL)
> >+        dObj->AddDataFlavor(flavorStr, &rtfFE);
> 
> The earlier call to GetFormat already called RegisterClipboardFormat, no?
Yes it did. I wasn't sure. Registering the same thing twice does no harm, but I'll see whether I can lose one call.

> In the meantime,
> bug 966986 makes this patch not apply anymore, so I wasn't able to test
> myself. Can you provide an updated patch?
Yes, that bug shredded right through it. I'll see how to adapt my patch.

The problem is that I don't have a Mac to compile on, so I have to send that to the try server for compilation which makes the turnaround slower.
Attached patch Proposed solution (v3) (obsolete) — Splinter Review
Addressed review issues.

> The earlier call to GetFormat already called RegisterClipboardFormat, no?
Indeed, my mistake, I didn't read the code properly and didn't see the earlier call to GetFormat() in the same function. I have removed the second registration and of course it works just the same.

Doing a Mac compile here:
https://treeherder.mozilla.org/#/jobs?repo=try&revision=30b95bb302cb
Attachment #8685066 - Attachment is obsolete: true
Attached patch Proposed solution (v3b) (obsolete) — Splinter Review
Oops, didn't correctly undo the change in nsITransferable.idl. Now it's good.
Attachment #8690359 - Attachment is obsolete: true
Attached patch Proposed solution (v4) (obsolete) — Splinter Review
v3 and v3b didn't work on Mac with drag, v4 does.

Download here:
https://archive.mozilla.org/pub/firefox/try-builds/mozilla@jorgk.com-b4bbd8da11bf77f67bc79a9abf69b1cd914ac8a5/try-macosx64/
Attachment #8690360 - Attachment is obsolete: true
Attachment #8690400 - Flags: review?(enndeakin)
Comment on attachment 8690400 [details] [diff] [review]
Proposed solution (v4)

>-  if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kNativeHTMLMime) == 0 ) {
>+  if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kNativeHTMLMime) == 0 ||
>+       strcmp(aFlavor,kRTFMime) == 0) {
>     nsCOMPtr<nsISupportsCString> primitive =
>         do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
>     if ( primitive ) {
>       const char * start = reinterpret_cast<const char*>(aDataBuff);
>+      // RTF on Windows is known to sometimes deliver an extra null byte.
>+      if (aDataLen > 0 && start[aDataLen - 1] == '\0')
>+        aDataLen--;

I would prefer to leave this intact, or have this done in nsClipboard::GetDataFromDataObject only for RTF data before CreatePrimitiveForData is called.

Otherwise this worked ok when I tried it on Mac!
Attachment #8690400 - Flags: review?(enndeakin) → review+
(In reply to Neil Deakin from comment #37)
> I would prefer to leave this intact, or have this done in
> nsClipboard::GetDataFromDataObject only for RTF data before
> CreatePrimitiveForData is called.
Your wish is my command ;-)
Would you kindly do an interdiff and stick another r+ onto it.
Also, does this apply without bug 586587? I haven't tried since I always assumed that bug 586587 would land first. Can I expect the review soon?
Attachment #8690400 - Attachment is obsolete: true
Attachment #8690855 - Flags: review?(enndeakin)
Looks like you can turn those around with a bit of fuzz:

jorgk@SAPO ~/mozilla-central
$ hg qpush 938991.patch
applying 938991.patch
patching file widget/windows/nsClipboard.cpp
Hunk #1 succeeded at 91 with fuzz 1 (offset 0 lines).
now at: 938991.patch

jorgk@SAPO ~/mozilla-central
$ hg qpush 586587.patch
applying 586587.patch
patching file widget/windows/nsClipboard.cpp
Hunk #1 succeeded at 100 with fuzz 1 (offset 2 lines).
now at: 586587.patch

Anyway, what about bug 586587 ;-)
Comment on attachment 8690855 [details] [diff] [review]
Proposed solution (v4b).

> Also, does this apply without bug 586587?

It applies OK, but doesn't work, since the DataTransfer.cpp change, and possibly others, are needed to handle single-bytes strings.

Can you set the bug dependency as needed?
Attachment #8690855 - Flags: review?(enndeakin) → review+
Well, usually I write a nice paragraph for the sheriff, like so:

Dear Sheriff,
Can you please check this in together with bug 586587.
Can you please check in the other bug first, so the patches apply cleanly.

Many thanks in advance.
Status: NEW → ASSIGNED
Keywords: checkin-needed
Depends on: 586587
https://hg.mozilla.org/mozilla-central/rev/0c34cc2dc429
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla45
Guys,

Nice job.

Please look at Bug 1233162 - Allow clipboardData.clearData() and clipboardData.setData during onpaste event

What you have done opens up nice opportunities, but without allowing clearData and setData during the onpaste event, the user experience becomes complicated with extra steps (1 - paste gets rtf and converts it to html, 2 - copy to clear clipboard and put custom html on it, 3 - paste the custom html).

Thanks,

Alex
If you look at the patch that was landed for this bug (https://hg.mozilla.org/mozilla-central/rev/0c34cc2dc429), you will see that the change was minimal. We just adjusted the existing framework so it now also works for RTF data. What you're asking for in bug 1233162 would entail a more serious change.
Thanks a lot for your effort.

clipboardData.getData('text/rtf') works great, but setData('text/rtf', rtfString) doesn't work.

function onCopy(e){
  e.preventDefault();
  var rtfString = '{\\rtf1\\ansi\\ansicpg936\\deff0\\nouicompat\\deflang1033\\deflangfe2052{\\fonttbl{\\f0\\fnil\\fcharset0 Calibri;}}\\uc1\\pard\\sa200\\sl276\\slmult1\\ul\\b\\i\\f0\\fs22\\lang9 How are you}';
e.clipboardData.setData('text/rtf', rtfString);
}

On WordPad, it pasted a "{", on Word, it pasted following:

_r_t_f_1_a_n_s_i_a_n_s_i_c_p_g_9_3_6_d_e_f_f_0_n_o_u_i_c_o_m_p_a_t_d_e_f_l_a_n_g_1_0_3_3_d_e_f_l_a_n_g_f_e_2_0_5_2__f_o_n_t_t_b_l__f_0_f_n_i_l_f_c_h_a_r_s_e_t_0_ _C_a_l_i_b_r_i_;___u_c_1_p_a_r_d_s_a_2_0_0_s_l_2_7_6_s_l_m_u_l_t_1_u_l_b_i_f_0_f_s_2_2_l_a_n_g_9_ _H_o_w_ _a_r_e_ _y_o_u_
Please raise another bug for this: "Setting clipboard data to text/rtf" doesn't work".
Depends on: 1254980
No longer depends on: 1254980
I think this may have been broken on Firefox 52.0 (and later, I tested it up to the 56.0a1 nightly), at least on OSX (10.12.5). If I have RTF-formatted text in my clipboard and paste it into a contenteditable paragraph, it pastes as plaintext (the same output as, say, `pbpaste`). I verified everything was working on 51.0.1 and went through the 52.0 release notes, but couldn't find anything that might have broken it.
Pasting RTF was implemented to support special editors (like TinyMCE and others). You could never paste RTF directly into a contenteditable. So I'm not sure what exactly "was working" in FF 51 and isn't working now.

That said, this function isn't covered by tests, so any breakage would go unnoticed.

Andrew, have you been following recent FF releases to make sure this doesn't break? Is there a test page with one of your editors in action? I've tried https://textbox.io/ but the RTF paste doesn't appear to be working there.

The simple test attached here (attachment 8682948 [details]) still seems to deliver the RTF correctly to the paste listener.
Flags: needinfo?(andrew.herron)
RTF pasting still works, tested in FF54.0.1. As long as the simple test still works it will probably work in the editor too, but the document attached to the sample may not show that when pasted into the editor.
Flags: needinfo?(andrew.herron)
Jorg, yes I'm using an editor, but it's just handling paste via the focus-switching pattern (that a lot of editors use), rather than trying to grab data from the paste event directly: https://github.com/quilljs/quill/blob/master/modules/clipboard.js#L105
I have only ever seen RTF paste working on a page specifically prepared by Andrew. I do not have access to this page any more. If he says that it's still working and the test from attachment 8682948 [details] is still working, then it must still be working. You will notice that Andrew didn't nominate a test page, perhaps he only discloses the functionality of pasting from MS Word via RTF to registered customers.

All I can say is that what was implemented here was to supply RTF data to paste or drop listeners for further processing.
I didn't intentionally hide the test page, I was using https://textbox.io to confirm. It's the attached word doc that was designed as the smallest possible replication case rather than show exactly what we're doing. But as I said if the attached test still works the editor will too.

@nelson this bug is specific to exposing the "text/rtf" data type via the `e.clipboardData` API in a paste event. It had no impact on the default ContentEditable paste behaviour. I suggest logging a new bug regarding your issue.
Hmm, using https://textbox.io, I'm pasting a red bold text from Wordpad (don't have Word on this machine). The listener sees:

pasteListener
length = 2
type:text/rtf
data:{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang3081{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;}
\uc1 
\pard\sa200\sl276\slmult1\cf1\b\f0\fs22\lang9 jkjk }
type:text/plain
data:jkjk

However, at https://textbox.io I don't see red or bold.
Ah, that'll do it. The editor only runs paste handling code when it detects the paste is coming from MS Word. I think we're getting a bit off topic for this closed bug though, feel free to ping me via email if you want to continue the discussion :)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: