Changeset 252561 in webkit


Ignore:
Timestamp:
Nov 18, 2019 1:22:31 PM (4 years ago)
Author:
Wenson Hsieh
Message:

[Clipboard API] Add support for Clipboard.writeText()
https://bugs.webkit.org/show_bug.cgi?id=204287
<rdar://problem/57270440>

Reviewed by Tim Horton.

LayoutTests/imported/w3c:

Rebaseline a couple of web platform tests.

  • web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https-expected.txt:
  • web-platform-tests/clipboard-apis/async-write-text-read-dttext-manual.https-expected.txt:

Source/WebCore:

Implements the writeText method on Clipboard.

Tests: editing/async-clipboard/clipboard-write-text-requires-user-gesture.html

editing/async-clipboard/clipboard-write-text.html

  • Modules/async-clipboard/Clipboard.cpp:

(WebCore::shouldProceedWithClipboardWrite):

Move this helper function further up the file.

(WebCore::Clipboard::writeText):

Implement the API, by populating a single PasteboardCustomData and writing it to the system clipboard.

LayoutTests:

  • editing/async-clipboard/clipboard-write-text-expected.txt: Added.
  • editing/async-clipboard/clipboard-write-text-requires-user-gesture-expected.txt: Added.
  • editing/async-clipboard/clipboard-write-text-requires-user-gesture.html: Added.

Add a layout test to verify that navigator.clipboard.writeText is gated on user gesture, if the
javaScriptCanAccessClipboard preference is disabled.

  • editing/async-clipboard/clipboard-write-text.html: Added.

Add a layout test to verify that writing text to the clipboard puts a single item on the system clipboard with
the type "text/plain", which can be read back using navigator.clipboard.read.

  • platform/win/TestExpectations:

Skip clipboard-write-text.html on Windows, since custom pasteboard data is not implemented there yet.

Location:
trunk
Files:
4 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r252540 r252561  
     12019-11-18  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [Clipboard API] Add support for Clipboard.writeText()
     4        https://bugs.webkit.org/show_bug.cgi?id=204287
     5        <rdar://problem/57270440>
     6
     7        Reviewed by Tim Horton.
     8
     9        * editing/async-clipboard/clipboard-write-text-expected.txt: Added.
     10        * editing/async-clipboard/clipboard-write-text-requires-user-gesture-expected.txt: Added.
     11        * editing/async-clipboard/clipboard-write-text-requires-user-gesture.html: Added.
     12
     13        Add a layout test to verify that `navigator.clipboard.writeText` is gated on user gesture, if the
     14        javaScriptCanAccessClipboard preference is disabled.
     15
     16        * editing/async-clipboard/clipboard-write-text.html: Added.
     17
     18        Add a layout test to verify that writing text to the clipboard puts a single item on the system clipboard with
     19        the type "text/plain", which can be read back using `navigator.clipboard.read`.
     20
     21        * platform/win/TestExpectations:
     22
     23        Skip clipboard-write-text.html on Windows, since custom pasteboard data is not implemented there yet.
     24
    1252019-11-18  Antoine Quint  <graouts@apple.com>
    226
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r252537 r252561  
     12019-11-18  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [Clipboard API] Add support for Clipboard.writeText()
     4        https://bugs.webkit.org/show_bug.cgi?id=204287
     5        <rdar://problem/57270440>
     6
     7        Reviewed by Tim Horton.
     8
     9        Rebaseline a couple of web platform tests.
     10
     11        * web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https-expected.txt:
     12        * web-platform-tests/clipboard-apis/async-write-text-read-dttext-manual.https-expected.txt:
     13
    1142019-11-17  Ryosuke Niwa  <rniwa@webkit.org>
    215
  • trunk/LayoutTests/imported/w3c/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https-expected.txt

    r251279 r252561  
    55PASS navigator.clipboard.write(null) fails (expect DataTransfer)
    66PASS navigator.clipboard.write(DOMString) fails (expect DataTransfer)
    7 FAIL navigator.clipboard.writeText(DOMString) succeeds promise_test: Unhandled rejection with value: object "NotSupportedError: The operation is not supported."
     7PASS navigator.clipboard.writeText(DOMString) succeeds
    88PASS navigator.clipboard.writeText() fails (expect DOMString)
    99FAIL navigator.clipboard.read() succeeds assert_true: expected true got false
  • trunk/LayoutTests/imported/w3c/web-platform-tests/clipboard-apis/async-write-text-read-dttext-manual.https-expected.txt

    r250856 r252561  
    11Note: This is a manual test because it writes/reads to the shared system clipboard and thus cannot be run async with other tests that might interact with the clipboard.
    22
    3 Harness Error (TIMEOUT), message = null
     3FAIL Verify write and read clipboard (DOMString) undefined is not an object (evaluating 'data.items.length')
    44
    5 TIMEOUT Verify write and read clipboard (DOMString) Test timed out
    6 
  • trunk/LayoutTests/platform/win/TestExpectations

    r252507 r252561  
    11941194webkit.org/b/203100 editing/async-clipboard/clipboard-write-basic.html [ Skip ]
    11951195webkit.org/b/203100 editing/async-clipboard/clipboard-write-items-twice.html [ Skip ]
     1196webkit.org/b/203100 editing/async-clipboard/clipboard-write-text.html [ Skip ]
    11961197
    11971198webkit.org/b/140783 [ Release ] editing/pasteboard/copy-standalone-image.html [ Failure ImageOnlyFailure ]
  • trunk/Source/WebCore/ChangeLog

    r252554 r252561  
     12019-11-18  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [Clipboard API] Add support for Clipboard.writeText()
     4        https://bugs.webkit.org/show_bug.cgi?id=204287
     5        <rdar://problem/57270440>
     6
     7        Reviewed by Tim Horton.
     8
     9        Implements the `writeText` method on Clipboard.
     10
     11        Tests: editing/async-clipboard/clipboard-write-text-requires-user-gesture.html
     12               editing/async-clipboard/clipboard-write-text.html
     13
     14        * Modules/async-clipboard/Clipboard.cpp:
     15        (WebCore::shouldProceedWithClipboardWrite):
     16
     17        Move this helper function further up the file.
     18
     19        (WebCore::Clipboard::writeText):
     20
     21        Implement the API, by populating a single PasteboardCustomData and writing it to the system clipboard.
     22
    1232019-11-18  Antoine Quint  <graouts@apple.com>
    224
  • trunk/Source/WebCore/Modules/async-clipboard/Clipboard.cpp

    r252450 r252561  
    2828
    2929#include "ClipboardItem.h"
     30#include "Document.h"
    3031#include "Frame.h"
    3132#include "JSBlob.h"
     
    4546WTF_MAKE_ISO_ALLOCATED_IMPL(Clipboard);
    4647
    47 Ref<Clipboard> Clipboard::create(Navigator& navigator)
    48 {
    49     return adoptRef(*new Clipboard(navigator));
    50 }
    51 
    52 Clipboard::Clipboard(Navigator& navigator)
    53     : m_navigator(makeWeakPtr(navigator))
    54 {
    55 }
    56 
    57 Clipboard::~Clipboard()
    58 {
    59     if (auto writer = WTFMove(m_activeItemWriter))
    60         writer->invalidate();
    61 }
    62 
    63 Navigator* Clipboard::navigator()
    64 {
    65     return m_navigator.get();
    66 }
    67 
    68 EventTargetInterface Clipboard::eventTargetInterface() const
    69 {
    70     return ClipboardEventTargetInterfaceType;
    71 }
    72 
    73 ScriptExecutionContext* Clipboard::scriptExecutionContext() const
    74 {
    75     return m_navigator ? m_navigator->scriptExecutionContext() : nullptr;
    76 }
    77 
    78 void Clipboard::readText(Ref<DeferredPromise>&& promise)
    79 {
    80     promise->reject(NotSupportedError);
    81 }
    82 
    83 void Clipboard::writeText(const String& data, Ref<DeferredPromise>&& promise)
    84 {
    85     UNUSED_PARAM(data);
    86     promise->reject(NotSupportedError);
    87 }
    88 
    89 void Clipboard::read(Ref<DeferredPromise>&& promise)
    90 {
    91     auto rejectPromiseAndClearActiveSession = [&] {
    92         m_activeSession = WTF::nullopt;
    93         promise->reject(NotAllowedError);
    94     };
    95 
    96     auto frame = makeRefPtr(this->frame());
    97     if (!frame) {
    98         rejectPromiseAndClearActiveSession();
    99         return;
    100     }
    101 
    102     auto pasteboard = Pasteboard::createForCopyAndPaste();
    103     auto changeCountAtStart = pasteboard->changeCount();
    104 
    105     if (!frame->requestDOMPasteAccess()) {
    106         rejectPromiseAndClearActiveSession();
    107         return;
    108     }
    109 
    110     if (!m_activeSession || m_activeSession->changeCount != changeCountAtStart) {
    111         auto allInfo = pasteboard->allPasteboardItemInfo();
    112         if (!allInfo) {
    113             rejectPromiseAndClearActiveSession();
    114             return;
    115         }
    116 
    117         Vector<Ref<ClipboardItem>> clipboardItems;
    118         clipboardItems.reserveInitialCapacity(allInfo->size());
    119         for (auto& itemInfo : *allInfo)
    120             clipboardItems.uncheckedAppend(ClipboardItem::create(*this, itemInfo));
    121         m_activeSession = {{ WTFMove(pasteboard), WTFMove(clipboardItems), changeCountAtStart }};
    122     }
    123 
    124     promise->resolve<IDLSequence<IDLInterface<ClipboardItem>>>(m_activeSession->items);
    125 }
    126 
    127 void Clipboard::getType(ClipboardItem& item, const String& type, Ref<DeferredPromise>&& promise)
    128 {
    129     if (!m_activeSession) {
    130         promise->reject(NotAllowedError);
    131         return;
    132     }
    133 
    134     auto frame = makeRefPtr(this->frame());
    135     if (!frame) {
    136         m_activeSession = WTF::nullopt;
    137         promise->reject(NotAllowedError);
    138         return;
    139     }
    140 
    141     auto itemIndex = m_activeSession->items.findMatching([&] (auto& activeItem) {
    142         return activeItem.ptr() == &item;
    143     });
    144 
    145     if (itemIndex == notFound) {
    146         promise->reject(NotAllowedError);
    147         return;
    148     }
    149 
    150     if (!item.types().contains(type)) {
    151         promise->reject(NotAllowedError);
    152         return;
    153     }
    154 
    155     String resultAsString;
    156 
    157     if (type == "text/uri-list"_s) {
    158         String title;
    159         resultAsString = activePasteboard().readURL(itemIndex, title).string();
    160     }
    161 
    162     if (type == "text/plain"_s) {
    163         PasteboardPlainText plainTextReader;
    164         activePasteboard().read(plainTextReader, PlainTextURLReadingPolicy::IgnoreURL, itemIndex);
    165         resultAsString = WTFMove(plainTextReader.text);
    166     }
    167 
    168     if (type == "text/html"_s) {
    169         WebContentMarkupReader markupReader { *frame };
    170         activePasteboard().read(markupReader, WebContentReadingPolicy::OnlyRichTextTypes, itemIndex);
    171         resultAsString = WTFMove(markupReader.markup);
    172     }
    173 
    174     // FIXME: Support reading "image/png" as well as custom data.
    175     // FIXME: Instead of checking changeCount here, we should send the changeCount over to the UI process to be vetted
    176     // when attempting to read the data in the first place.
    177     if (m_activeSession->changeCount != activePasteboard().changeCount()) {
    178         m_activeSession = WTF::nullopt;
    179         promise->reject(NotAllowedError);
    180         return;
    181     }
    182 
    183     if (!resultAsString.isNull())
    184         promise->resolve<IDLInterface<Blob>>(ClipboardItem::blobFromString(resultAsString, type));
    185     else
    186         promise->reject(NotAllowedError);
    187 }
    188 
    18948static bool shouldProceedWithClipboardWrite(const Frame& frame)
    19049{
     
    20463    ASSERT_NOT_REACHED();
    20564    return false;
     65}
     66
     67Ref<Clipboard> Clipboard::create(Navigator& navigator)
     68{
     69    return adoptRef(*new Clipboard(navigator));
     70}
     71
     72Clipboard::Clipboard(Navigator& navigator)
     73    : m_navigator(makeWeakPtr(navigator))
     74{
     75}
     76
     77Clipboard::~Clipboard()
     78{
     79    if (auto writer = WTFMove(m_activeItemWriter))
     80        writer->invalidate();
     81}
     82
     83Navigator* Clipboard::navigator()
     84{
     85    return m_navigator.get();
     86}
     87
     88EventTargetInterface Clipboard::eventTargetInterface() const
     89{
     90    return ClipboardEventTargetInterfaceType;
     91}
     92
     93ScriptExecutionContext* Clipboard::scriptExecutionContext() const
     94{
     95    return m_navigator ? m_navigator->scriptExecutionContext() : nullptr;
     96}
     97
     98void Clipboard::readText(Ref<DeferredPromise>&& promise)
     99{
     100    promise->reject(NotSupportedError);
     101}
     102
     103void Clipboard::writeText(const String& data, Ref<DeferredPromise>&& promise)
     104{
     105    auto frame = makeRefPtr(this->frame());
     106    auto document = makeRefPtr(frame ? frame->document() : nullptr);
     107    if (!document || !frame || !shouldProceedWithClipboardWrite(*frame)) {
     108        promise->reject(NotAllowedError);
     109        return;
     110    }
     111
     112    PasteboardCustomData customData;
     113    customData.writeString("text/plain"_s, data);
     114    customData.setOrigin(document->originIdentifierForPasteboard());
     115    Pasteboard::createForCopyAndPaste()->writeCustomData({ WTFMove(customData) });
     116    promise->resolve();
     117}
     118
     119void Clipboard::read(Ref<DeferredPromise>&& promise)
     120{
     121    auto rejectPromiseAndClearActiveSession = [&] {
     122        m_activeSession = WTF::nullopt;
     123        promise->reject(NotAllowedError);
     124    };
     125
     126    auto frame = makeRefPtr(this->frame());
     127    if (!frame) {
     128        rejectPromiseAndClearActiveSession();
     129        return;
     130    }
     131
     132    auto pasteboard = Pasteboard::createForCopyAndPaste();
     133    auto changeCountAtStart = pasteboard->changeCount();
     134
     135    if (!frame->requestDOMPasteAccess()) {
     136        rejectPromiseAndClearActiveSession();
     137        return;
     138    }
     139
     140    if (!m_activeSession || m_activeSession->changeCount != changeCountAtStart) {
     141        auto allInfo = pasteboard->allPasteboardItemInfo();
     142        if (!allInfo) {
     143            rejectPromiseAndClearActiveSession();
     144            return;
     145        }
     146
     147        Vector<Ref<ClipboardItem>> clipboardItems;
     148        clipboardItems.reserveInitialCapacity(allInfo->size());
     149        for (auto& itemInfo : *allInfo)
     150            clipboardItems.uncheckedAppend(ClipboardItem::create(*this, itemInfo));
     151        m_activeSession = {{ WTFMove(pasteboard), WTFMove(clipboardItems), changeCountAtStart }};
     152    }
     153
     154    promise->resolve<IDLSequence<IDLInterface<ClipboardItem>>>(m_activeSession->items);
     155}
     156
     157void Clipboard::getType(ClipboardItem& item, const String& type, Ref<DeferredPromise>&& promise)
     158{
     159    if (!m_activeSession) {
     160        promise->reject(NotAllowedError);
     161        return;
     162    }
     163
     164    auto frame = makeRefPtr(this->frame());
     165    if (!frame) {
     166        m_activeSession = WTF::nullopt;
     167        promise->reject(NotAllowedError);
     168        return;
     169    }
     170
     171    auto itemIndex = m_activeSession->items.findMatching([&] (auto& activeItem) {
     172        return activeItem.ptr() == &item;
     173    });
     174
     175    if (itemIndex == notFound) {
     176        promise->reject(NotAllowedError);
     177        return;
     178    }
     179
     180    if (!item.types().contains(type)) {
     181        promise->reject(NotAllowedError);
     182        return;
     183    }
     184
     185    String resultAsString;
     186
     187    if (type == "text/uri-list"_s) {
     188        String title;
     189        resultAsString = activePasteboard().readURL(itemIndex, title).string();
     190    }
     191
     192    if (type == "text/plain"_s) {
     193        PasteboardPlainText plainTextReader;
     194        activePasteboard().read(plainTextReader, PlainTextURLReadingPolicy::IgnoreURL, itemIndex);
     195        resultAsString = WTFMove(plainTextReader.text);
     196    }
     197
     198    if (type == "text/html"_s) {
     199        WebContentMarkupReader markupReader { *frame };
     200        activePasteboard().read(markupReader, WebContentReadingPolicy::OnlyRichTextTypes, itemIndex);
     201        resultAsString = WTFMove(markupReader.markup);
     202    }
     203
     204    // FIXME: Support reading "image/png" as well as custom data.
     205    // FIXME: Instead of checking changeCount here, we should send the changeCount over to the UI process to be vetted
     206    // when attempting to read the data in the first place.
     207    if (m_activeSession->changeCount != activePasteboard().changeCount()) {
     208        m_activeSession = WTF::nullopt;
     209        promise->reject(NotAllowedError);
     210        return;
     211    }
     212
     213    if (!resultAsString.isNull())
     214        promise->resolve<IDLInterface<Blob>>(ClipboardItem::blobFromString(resultAsString, type));
     215    else
     216        promise->reject(NotAllowedError);
    206217}
    207218
Note: See TracChangeset for help on using the changeset viewer.