cool#9045 - close clipboard race by waiting for completion.
To avoid the HTTP[S] request racing the websocket and sometimes loosing we need to: * get a notification from the Kit when the copy / cut is complete * wait on a Promise for this, to allow the HTTP fetch to start * re-work to do a single, rather than two fetches by sharing the download promise. Change-Id: Ic23f7f817cc855ff08f25a2afefcd73d6fc3472b Signed-off-by: Michael Meeks <michael.meeks@collabora.com>pull/9033/head
parent
0ef5e740a4
commit
ff8dbe7fde
|
@ -13,7 +13,7 @@
|
|||
* local & remote clipboard data.
|
||||
*/
|
||||
|
||||
/* global app _ brandProductName $ ClipboardItem */
|
||||
/* global app _ brandProductName $ ClipboardItem Promise */
|
||||
|
||||
// Get all interesting clipboard related events here, and handle
|
||||
// download logic in one place ...
|
||||
|
@ -38,6 +38,10 @@ L.Clipboard = L.Class.extend({
|
|||
this._dummyPlainDiv = null;
|
||||
this._dummyClipboard = {};
|
||||
|
||||
// Tracks waiting for UNO commands to complete
|
||||
this._commandCompletion = [];
|
||||
this._map.on('commandresult', this._onCommandResult, this);
|
||||
|
||||
div.setAttribute('id', this._dummyDivName);
|
||||
div.setAttribute('style', 'user-select: text !important');
|
||||
div.style.opacity = '0';
|
||||
|
@ -818,6 +822,25 @@ L.Clipboard = L.Class.extend({
|
|||
this.paste(ev);
|
||||
},
|
||||
|
||||
// Gets status of a copy/paste command from the remote Kit
|
||||
_onCommandResult: function(e) {
|
||||
if (e.commandName === '.uno:Copy' || e.commandName === '.uno:Cut')
|
||||
{
|
||||
window.app.console.log('Resolve clipboard command promise ' + e.commandName);
|
||||
const that = this;
|
||||
while (that._commandCompletion.length > 0)
|
||||
{
|
||||
let a = that._commandCompletion.shift();
|
||||
a.resolve(a.fetch.then(function(text) {
|
||||
const content = that.parseClipboard(text)[a.shorttype];
|
||||
const blob = new Blob([content], { 'type': a.mimetype });
|
||||
console.log('Generate blob of type ' + a.mimetype + ' from ' +a.shorttype + ' text: ' +content);
|
||||
return blob;
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Executes the navigator.clipboard.write() call, if it's available.
|
||||
_navigatorClipboardWrite: function() {
|
||||
if (!L.Browser.hasNavigatorClipboardWrite) {
|
||||
|
@ -828,26 +851,44 @@ L.Clipboard = L.Class.extend({
|
|||
return false;
|
||||
}
|
||||
|
||||
app.socket.sendMessage('uno ' + this._unoCommandForCopyCutPaste);
|
||||
const url = this.getMetaURL() + '&MimeType=text/html,text/plain;charset=utf-8';
|
||||
const command = this._unoCommandForCopyCutPaste;
|
||||
app.socket.sendMessage('uno ' + command);
|
||||
|
||||
// This is sent down the websocket URL which can race with the
|
||||
// web fetch - so first step is to wait for the result of
|
||||
// that command so we are sure the clipboard is set before
|
||||
// fetching it.
|
||||
|
||||
const that = this;
|
||||
|
||||
if (that._commandCompletion.length > 0)
|
||||
window.app.console.error('Already have ' + that._commandCompletion.length +
|
||||
' pending clipboard command(s)');
|
||||
|
||||
const url = that.getMetaURL() + '&MimeType=text/html,text/plain;charset=utf-8';
|
||||
|
||||
// Share a single fetch
|
||||
var fetchPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
var result = fetch(url).then(response => response.text());
|
||||
resolve(result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
var awaitPromise = function(url, mimetype, shorttype) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.app.console.log('New ' + command + ' promise');
|
||||
// FIXME: add a timeout cleanup too ...
|
||||
that._commandCompletion.push({ fetch: fetchPromise, command: command,
|
||||
resolve: resolve, reject: reject,
|
||||
mimetype: mimetype, shorttype: shorttype});
|
||||
}); };
|
||||
|
||||
const text = new ClipboardItem({
|
||||
'text/html': fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(function(text) {
|
||||
const type = "text/html";
|
||||
const content = that.parseClipboard(text)['html'];
|
||||
const blob = new Blob([content], { 'type': type });
|
||||
return blob;
|
||||
}),
|
||||
'text/plain': fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(function(text) {
|
||||
const type = 'text/plain';
|
||||
const content = that.parseClipboard(text)['plain'];
|
||||
const blob = new Blob([content], { 'type': type });
|
||||
return blob;
|
||||
}),
|
||||
'text/html': awaitPromise(url, 'text/html', 'html'),
|
||||
'text/plain': awaitPromise(url, 'text/plain', 'plain')
|
||||
});
|
||||
let clipboard = navigator.clipboard;
|
||||
if (L.Browser.cypressTest) {
|
||||
|
|
|
@ -223,8 +223,9 @@ function assertSheetContents(expectedData, copy) {
|
|||
function assertDataClipboardTable(expectedData) {
|
||||
cy.log('>> assertDataClipboardTable - start');
|
||||
|
||||
cy.cGet('#copy-paste-container table td').should(function($td) {
|
||||
expect($td).to.have.length(expectedData.length);
|
||||
cy.cGet('#copy-paste-container table td')
|
||||
.should('have.length', expectedData.length)
|
||||
.should(function($td) {
|
||||
var actualData = $td.map(function(i,el) {
|
||||
return Cypress.$(el).text();
|
||||
}).get();
|
||||
|
|
|
@ -1958,6 +1958,8 @@ bool ChildSession::unoCommand(const StringVector& tokens)
|
|||
const bool bNotify = (tokens.equals(1, ".uno:Save") ||
|
||||
tokens.equals(1, ".uno:Undo") ||
|
||||
tokens.equals(1, ".uno:Redo") ||
|
||||
tokens.equals(1, ".uno:Cut") ||
|
||||
tokens.equals(1, ".uno:Copy") ||
|
||||
tokens.equals(1, ".uno:OpenHyperlink") ||
|
||||
tokens.startsWith(1, "vnd.sun.star.script:"));
|
||||
|
||||
|
|
|
@ -242,9 +242,10 @@ dialog <command>
|
|||
|
||||
<command> is unique identifier for the dialog that needs to be painted.
|
||||
|
||||
uno <command>
|
||||
uno <command> [arguments]
|
||||
|
||||
<command> is a line of text.
|
||||
[argments] - JSON encoded arguments for the UNO command
|
||||
|
||||
save dontTerminateEdit=<value> dontSaveIfUnmodified=<value>
|
||||
|
||||
|
|
Loading…
Reference in New Issue