diff options
Diffstat (limited to 'widget/tests/browser/browser_test_clipboardcache.js')
-rw-r--r-- | widget/tests/browser/browser_test_clipboardcache.js | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/widget/tests/browser/browser_test_clipboardcache.js b/widget/tests/browser/browser_test_clipboardcache.js new file mode 100644 index 0000000000..6da1be9a26 --- /dev/null +++ b/widget/tests/browser/browser_test_clipboardcache.js @@ -0,0 +1,145 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +// Note: widget/tests/test_bug1123480.xhtml checks whether nsTransferable behaves +// as expected with regards to private browsing mode and the clipboard cache, +// i.e. that the clipboard is not cached to the disk when private browsing mode +// is enabled. +// +// This test tests that the clipboard is not cached to the disk by IPC, +// as a regression test for bug 1396224. +// It indirectly uses nsTransferable, via the async navigator.clipboard API. + +// Create over 1 MB of sample garbage text. JavaScript strings are represented +// by UTF16 strings, so the size is twice as much as the actual string length. +// This value is chosen such that the size of the memory for the string exceeds +// the kLargeDatasetSize threshold in nsTransferable.h. +// It is also not a round number to reduce the odds of having an accidental +// collisions with another file (since the test below looks at the file size +// to identify the file). +var Ipsum = "0123456789".repeat(1234321); +var IpsumByteLength = Ipsum.length * 2; +var SHORT_STRING_NO_CACHE = "short string that will not be cached to the disk"; + +// Get a list of open file descriptors that refer to a file with the same size +// as the expected data (and assume that any mutations in file descriptor counts +// are caused by our test). +// TODO: This logic only counts file descriptors that are still open (e.g. when +// data persists after a copy). It does not detect cache files that exist only +// temporarily (e.g. after a paste). +function getClipboardCacheFDCount() { + let dir; + if (AppConstants.platform === "win") { + // On Windows, nsAnonymousTemporaryFile does not immediately delete a file. + // Instead, the Windows-specific FILE_FLAG_DELETE_ON_CLOSE flag is used, + // which means that the file is deleted when the last handle is closed. + // Apparently, this flag is unreliable (e.g. when the application crashes), + // so nsAnonymousTemporaryFile stores the temporary files in a subdirectory, + // which is cleaned up some time after start-up. + + // This is just a test, and during the test we deterministically close the + // handles, so if FILE_FLAG_DELETE_ON_CLOSE does the thing it promises, the + // file is actually removed when the handle is closed. + + let { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" + ); + // Path from nsAnonymousTemporaryFile.cpp, GetTempDir. + dir = FileUtils.getFile("TmpD", ["mozilla-temp-files"]); + } else { + dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + dir.initWithPath("/dev/fd"); + } + let count = 0; + for (let fdFile of dir.directoryEntries) { + let fileSize; + try { + fileSize = fdFile.fileSize; + } catch (e) { + // This can happen on macOS. + continue; + } + if (fileSize === IpsumByteLength) { + // Assume that the file was created by us if the size matches. + ++count; + } + } + return count; +} + +async function testCopyPaste(isPrivate) { + let win = await BrowserTestUtils.openNewBrowserWindow({ private: isPrivate }); + let tab = await BrowserTestUtils.openNewForegroundTab(win); + let browser = tab.linkedBrowser; + + // Sanitize environment + await ContentTask.spawn(browser, SHORT_STRING_NO_CACHE, async shortStr => { + await content.navigator.clipboard.writeText(shortStr); + }); + + let initialFdCount = getClipboardCacheFDCount(); + + await SpecialPowers.spawn(browser, [Ipsum], async largeString => { + await content.navigator.clipboard.writeText(largeString); + }); + + let fdCountAfterCopy = getClipboardCacheFDCount(); + if (isPrivate) { + is(fdCountAfterCopy, initialFdCount, "Private write"); + } else { + is(fdCountAfterCopy, initialFdCount + 1, "Cached write"); + } + + let readStr = await SpecialPowers.spawn(browser, [], async () => { + let { document } = content; + document.body.contentEditable = true; + document.body.focus(); + let pastePromise = new Promise(resolve => { + document.addEventListener( + "paste", + e => { + resolve(e.clipboardData.getData("text/plain")); + }, + { once: true } + ); + }); + document.execCommand("paste"); + return pastePromise; + }); + ok(readStr === Ipsum, "Read what we pasted"); + + if (isPrivate) { + is(getClipboardCacheFDCount(), fdCountAfterCopy, "Private read"); + } else { + // Upon reading from the clipboard, a temporary nsTransferable is used, for + // which the cache is disabled. The content process does not cache clipboard + // data either. So the file descriptor count should be identical. + is(getClipboardCacheFDCount(), fdCountAfterCopy, "Read not cached"); + } + + // Cleanup. + await SpecialPowers.spawn( + browser, + [SHORT_STRING_NO_CACHE], + async shortStr => { + await content.navigator.clipboard.writeText(shortStr); + } + ); + is(getClipboardCacheFDCount(), initialFdCount, "Drop clipboard cache if any"); + + BrowserTestUtils.removeTab(tab); + await BrowserTestUtils.closeWindow(win); +} + +add_task(async function test_private() { + await testCopyPaste(true); +}); + +add_task(async function test_non_private() { + await testCopyPaste(false); +}); |