diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f48dc01..764a1f290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# WIP + +* Sheet export + * most exports broken by Chrome 92, mostly fixed + * we discovered that CSV export was not working in any major browser, though it's unclear why. We've disabled CSV export in the meantime +* some new browser-specific checkup tests to make it easier to detect future regressions in the APIs needed for sheet export + # 4.9.0 ## Goals and announcements diff --git a/www/checkup/app-checkup.less b/www/checkup/app-checkup.less index a475d5983..026d1952c 100644 --- a/www/checkup/app-checkup.less +++ b/www/checkup/app-checkup.less @@ -14,11 +14,15 @@ html, body { .report { font-size: 30px; - max-width: 50%; + max-width: 26em; margin: auto; padding-top: 15px; } + .summary, .failure, .error, .success { + margin-bottom: 1em; + } + .pending { border: 1px solid @cryptpad_text_col; .fa { @@ -42,10 +46,18 @@ html, body { padding: 15px; } - table { - td { - padding: 5px; - border: 1px solid @cryptpad_text_col; + .table-container { + overflow-x: auto; + width: 100%; + table { + td { + padding: 5px; + border: 1px solid @cryptpad_text_col; + font-size: 60%; + } + td:nth-child(2) { + word-break: break-word; + } } } @@ -72,7 +84,12 @@ html, body { color: @cryptpad_color_link; } } - .cp-app-checkup-version { + + .cp-notice-browser, .cp-notice-details, .cp-notice-other { + font-size: 70%; + } + + .cp-app-checkup-version, .cp-app-checkup-browser { text-decoration: underline; } diff --git a/www/checkup/checkup-tools.js b/www/checkup/checkup-tools.js new file mode 100644 index 000000000..79fe1c4b3 --- /dev/null +++ b/www/checkup/checkup-tools.js @@ -0,0 +1,36 @@ +define([ +], function () { + var Tools = {}; + Tools.supportsSharedArrayBuffers = function () { + try { + return Object.prototype.toString.call(new window.WebAssembly.Memory({ + shared: true, + initial: 0, + maximum: 0, + }).buffer) === '[object SharedArrayBuffer]'; + } catch (err) { + console.error(err); + } + return false; + }; + + + Tools.isSafari = function () { + return navigator.vendor.match(/apple/i); + }; + + Tools.isChrome = function () { + return navigator.vendor.match(/google/i); + }; + + Tools.guessBrowser = function () { + if (Tools.isChrome()) { return 'chrome/blink'; } + if (Tools.isSafari()) { return 'safari/webkit'; } + if (navigator.userAgent.match(/firefox\//i)) { return 'firefox/gecko'; } + if (navigator.userAgent.match(/edge\//i)) { return 'edge/edgehtml'; } + if (navigator.userAgent.match(/trident\//i)) { return 'ie/trident'; } + return navigator.userAgent + "\n" + navigator.vendor; + }; + + return Tools; +}); diff --git a/www/checkup/main.js b/www/checkup/main.js index 4413d3b18..171b9ac3b 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -13,13 +13,14 @@ define([ '/common/pinpad.js', '/common/outer/network-config.js', '/customize/pages.js', + '/checkup/checkup-tools.js', '/bower_components/tweetnacl/nacl-fast.min.js', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/checkup/app-checkup.less', ], function ($, ApiConfig, Assertions, h, Messages, DomReady, nThen, SFCommonO, Login, Hash, Util, Pinpad, - NetConfig, Pages) { + NetConfig, Pages, Tools) { var Assert = Assertions(); var trimSlashes = function (s) { if (typeof(s) !== 'string') { return s; } @@ -703,6 +704,69 @@ define([ }); }); + var safariGripe = function () { + return h('p.cp-notice-other', 'This is expected because Safari and platforms that use its engine lack commonly supported functionality.'); + }; + + var browserIssue = function () { + return h('p.cp-notice-other', 'This test checks for the presence of features in your browser and is not necessarily caused by server misconfiguration.'); + }; + + assert(function (cb, msg) { + cb = Util.once(cb); + setWarningClass(msg); + var notice = h('span', [ + h('p', 'It appears that some features required for Office file format conversion are not present.'), + Tools.isSafari()? safariGripe(): undefined, + browserIssue(), + ]); + + msg.appendChild(notice); + + var expected = [ + 'Atomics', + 'SharedArrayBuffer', + 'WebAssembly', + ['WebAssembly', 'Memory'], + ['WebAssembly', 'instantiate'], + ['WebAssembly', 'instantiateStreaming'], + ['Buffer', 'from'], + + 'SharedWorker', + 'worker', + 'crossOriginIsolated', + ]; + + var responses = {}; + + nThen(function (w) { + deferredPostMessage({ + command: 'CHECK_JS_APIS', + content: { + globals: expected, + }, + }, w(function (response) { + Util.extend(responses, response); + })); + + deferredPostMessage({ + command: 'FANCY_API_CHECKS', + content: { + }, + }, w(function (response) { + Util.extend(responses, response); + })); + }).nThen(function () { + if (!responses.Atomics || !responses.WebAssembly) { + return void cb(responses); + } + if (responses.SharedArrayBuffer || responses.SharedArrayBufferFallback) { + return cb(true); + } + return void cb(response); + }); + }); + var isHTTPS = function (host) { return /^https:\/\//.test(host); }; @@ -831,17 +895,19 @@ define([ var failureReport = function (obj) { var printableValue = obj.output; try { - printableValue = JSON.stringify(obj.output); + printableValue = JSON.stringify(obj.output, null, ' '); } catch (err) { console.error(err); } return h('div.error', [ h('h5', obj.message), - h('table', [ - row(["Failed test number", obj.test + 1]), - row(["Returned value", code(printableValue)]), - ]), + h('div.table-container', + h('table', [ + row(["Failed test number", obj.test + 1]), + row(["Returned value", h('pre', code(printableValue))]), + ]), + ), ]); }; @@ -849,7 +915,7 @@ define([ var $progress = $('#cp-progress'); var versionStatement = function () { - return h('p', [ + return h('p.cp--notice-version', [ "This instance is running ", h('span.cp-app-checkup-version',[ "CryptPad", @@ -860,6 +926,16 @@ define([ ]); }; + var browserStatement = function () { + var name = Tools.guessBrowser(); + if (!name) { return; } + return h('p.cp-notice-browser', [ + "You appear to be using a ", + h('span.cp-app-checkup-browser', name), + ' browser to view this page.', + ]); + }; + Assert.run(function (state) { var errors = state.errors; var failed = errors.length; @@ -870,10 +946,11 @@ define([ var failedDetails = "Details found below"; var successDetails = "This checkup only tests the most common configuration issues. You may still experience errors or incorrect behaviour."; - var details = h('p', failed? failedDetails: successDetails); + var details = h('p.cp-notice-details', failed? failedDetails: successDetails); var summary = h('div.summary.' + statusClass, [ versionStatement(), + browserStatement(), h('p', Messages._getKey('assert_numberOfTestsPassed', [ state.passed, state.total diff --git a/www/checkup/sandbox/main.js b/www/checkup/sandbox/main.js index e11aa1d52..272db91a4 100644 --- a/www/checkup/sandbox/main.js +++ b/www/checkup/sandbox/main.js @@ -1,10 +1,12 @@ define([ 'jquery', + '/common/common-util.js', + '/checkup/checkup-tools.js', '/bower_components/tweetnacl/nacl-fast.min.js', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/checkup/app-checkup.less', -], function ($) { +], function ($, Util, Tools) { var postMessage = function (content) { window.parent.postMessage(JSON.stringify(content), '*'); }; @@ -26,6 +28,26 @@ define([ }); }; + COMMANDS.CHECK_JS_APIS = function (content, cb) { + var globalAPIs = content['globals'] || []; + var response = {}; + globalAPIs.forEach(function (key) { + if (Array.isArray(key)) { + response[key.join('.')] = Boolean(Util.find(window, key)); + return; + } + + response[key] = Boolean(window[key]); + }); + cb(response); + }; + + COMMANDS.FANCY_API_CHECKS = function (content, cb) { + cb({ + SharedArrayBufferFallback: Tools.supportsSharedArrayBuffers(), + }); + }; + window.addEventListener("message", function (event) { var txid, command; if (event && event.data) { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 4a0a56be0..2afef4978 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -72,8 +72,31 @@ define([ return JSONSortify(obj); }; + /* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts + where window.crossOriginIsolated is false. + + Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html) + isn't clear about why they're doing this, but since it's related to site-isolation + it seems they're trying to do vague security things. + + In any case, there seems to be a workaround where you can still create them + by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`. + + This seems unreliable, but it's better than not being able to export, since + we actively rely on postMessage between iframes and therefore can't afford + to opt for full isolation. + */ + var supportsSharedArrayBuffers = function () { + try { + return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]'; + } catch (err) { + console.error(err); + } + return false; + }; + var supportsXLSX = function () { - return !(typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined" || typeof(WebAssembly) === 'undefined'); + return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() /* || typeof (SharedArrayBuffer) === "undefined" */ || typeof(WebAssembly) === 'undefined'); }; @@ -1915,7 +1938,9 @@ define([ var exportXLSXFile = function() { var text = getContent(); var suggestion = Title.suggestTitle(Title.defaultTitle); - var ext = ['.xlsx', '.ods', '.bin', '.csv', '.pdf']; + var ext = ['.xlsx', '.ods', '.bin', + //'.csv', // XXX + '.pdf']; var type = common.getMetadataMgr().getPrivateData().ooType; var warning = ''; if (type==="presentation") { diff --git a/www/common/onlyoffice/x2t/x2t.js b/www/common/onlyoffice/x2t/x2t.js index 181b2db8f..9c1289c56 100644 --- a/www/common/onlyoffice/x2t/x2t.js +++ b/www/common/onlyoffice/x2t/x2t.js @@ -1,3 +1,8 @@ +function SUPPORTS_SHARED_MEMORY() { + return typeof(SharedArrayBuffer) !== 'undefined'; +} + + // Support for growable heap + pthreads, where the buffer may change, so JS views // must be updated. function GROWABLE_HEAP_STORE_I8(ptr, value) { @@ -1030,7 +1035,7 @@ if (ENVIRONMENT_IS_PTHREAD) { "maximum": 1073741824 / WASM_PAGE_SIZE, "shared": true }); - if (!(wasmMemory.buffer instanceof SharedArrayBuffer)) { + if (Object.prototype.toString.call(wasmMemory.buffer) !== '[object SharedArrayBuffer]') { err("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"); if (ENVIRONMENT_HAS_NODE) { console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)"); @@ -2161,7 +2166,7 @@ var PThread = { }), receiveObjectTransfer: (function(data) {}), allocateUnusedWorkers: (function(numWorkers, onFinishedLoading) { - if (typeof SharedArrayBuffer === "undefined") return; + if (!SUPPORTS_SHARED_MEMORY()) return; var workers = []; var numWorkersToCreate = numWorkers; if (PThread.preallocatedWorkers.length > 0) { @@ -2276,7 +2281,7 @@ var PThread = { } }), createNewWorkers: (function(numWorkers) { - if (typeof SharedArrayBuffer === "undefined") return []; + if (!SUPPORTS_SHARED_MEMORY()) return []; var pthreadMainJs = "x2t.worker.js"; pthreadMainJs = locateFile(pthreadMainJs); var newWorkers = []; @@ -5683,7 +5688,7 @@ function _emscripten_get_sbrk_ptr() { } Module["_emscripten_get_sbrk_ptr"] = _emscripten_get_sbrk_ptr; function _emscripten_has_threading_support() { - return typeof SharedArrayBuffer !== "undefined"; + return SUPPORTS_SHARED_MEMORY(); } Module["_emscripten_has_threading_support"] = _emscripten_has_threading_support; function _emscripten_is_main_browser_thread() { @@ -6761,7 +6766,7 @@ function _pthread_self() { } Module["_pthread_self"] = _pthread_self; function _pthread_create(pthread_ptr, attr, start_routine, arg) { - if (typeof SharedArrayBuffer === "undefined") { + if (!SUPPORTS_SHARED_MEMORY()) { err("Current environment does not support SharedArrayBuffer, pthreads are not available!"); return 6; } diff --git a/www/common/translations/messages.ja.json b/www/common/translations/messages.ja.json index 3d68dbab9..84fe9a3ae 100644 --- a/www/common/translations/messages.ja.json +++ b/www/common/translations/messages.ja.json @@ -560,7 +560,7 @@ "form_form": "フォーム", "form_editor": "エディタ", "form_delete": "削除", - "form_sent": "送信しました", + "form_sent": "回答を送信しました", "form_reset": "リセット", "form_update": "更新", "form_submit": "送信", @@ -1378,5 +1378,6 @@ "notification_linkShared": "{0}があなたとリンクを共有しました: {1}", "fm_link_name_placeholder": "あなたのリンク", "fm_link_warning": "注意:URLが200字を超えています", - "fm_link_invalid": "URLが無効です" + "fm_link_invalid": "URLが無効です", + "form_answerAs": "名前を記入してください" }