forked from mystiq/hydrogen-web
detect when sync promise hack is needed
This commit is contained in:
parent
706ec97296
commit
98a6d82820
2 changed files with 44 additions and 8 deletions
|
@ -27,6 +27,7 @@ import "mdn-polyfills/Element.prototype.closest";
|
||||||
// weighing a good extra 500kb :-(
|
// weighing a good extra 500kb :-(
|
||||||
import "text-encoding";
|
import "text-encoding";
|
||||||
import Promise from "../lib/es6-promise/lib/es6-promise/promise.js";
|
import Promise from "../lib/es6-promise/lib/es6-promise/promise.js";
|
||||||
|
import {checkNeedsSyncPromise} from "./matrix/storage/idb/utils.js";
|
||||||
|
|
||||||
const flush = Promise._flush;
|
const flush = Promise._flush;
|
||||||
Promise._flush = function() {
|
Promise._flush = function() {
|
||||||
|
@ -35,6 +36,8 @@ Promise._flush = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Promise = Promise;
|
window.Promise = Promise;
|
||||||
|
// TODO: should be awaited before opening any session in the picker
|
||||||
|
checkNeedsSyncPromise();
|
||||||
|
|
||||||
// TODO: contribute this to mdn-polyfills
|
// TODO: contribute this to mdn-polyfills
|
||||||
if (!Element.prototype.remove) {
|
if (!Element.prototype.remove) {
|
||||||
|
@ -42,3 +45,5 @@ if (!Element.prototype.remove) {
|
||||||
this.parentNode.removeChild(this);
|
this.parentNode.removeChild(this);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,36 @@ limitations under the License.
|
||||||
|
|
||||||
import { StorageError } from "../common.js";
|
import { StorageError } from "../common.js";
|
||||||
|
|
||||||
|
let needsSyncPromise = false;
|
||||||
|
|
||||||
|
/* should be called on legacy platforms to see
|
||||||
|
if transactions close before draining the microtask queue (IE11 on Windows 7).
|
||||||
|
If this is the case, promises need to be resolved
|
||||||
|
synchronously from the idb request handler to prevent the transaction from closing prematurely.
|
||||||
|
*/
|
||||||
|
export async function checkNeedsSyncPromise() {
|
||||||
|
// important to have it turned off while doing the test,
|
||||||
|
// otherwise reqAsPromise would not fail
|
||||||
|
needsSyncPromise = false;
|
||||||
|
const NAME = "test-idb-needs-sync-promise";
|
||||||
|
const db = await openDatabase(NAME, db => {
|
||||||
|
db.createObjectStore("test", {keyPath: "key"});
|
||||||
|
}, 1);
|
||||||
|
const txn = db.transaction("test", "readonly");
|
||||||
|
try {
|
||||||
|
await reqAsPromise(txn.objectStore("test").get(1));
|
||||||
|
await reqAsPromise(txn.objectStore("test").get(2));
|
||||||
|
} catch (err) {
|
||||||
|
// err.name would be either TransactionInactiveError or InvalidStateError,
|
||||||
|
// but let's not exclude any other failure modes
|
||||||
|
needsSyncPromise = true;
|
||||||
|
}
|
||||||
|
// we could delete the store here,
|
||||||
|
// but let's not create it on every page load on legacy platforms,
|
||||||
|
// and just keep it around
|
||||||
|
return needsSyncPromise;
|
||||||
|
}
|
||||||
|
|
||||||
class IDBRequestError extends StorageError {
|
class IDBRequestError extends StorageError {
|
||||||
constructor(request) {
|
constructor(request) {
|
||||||
const source = request?.source;
|
const source = request?.source;
|
||||||
|
@ -38,7 +68,7 @@ export function decodeUint32(str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openDatabase(name, createObjectStore, version) {
|
export function openDatabase(name, createObjectStore, version) {
|
||||||
const req = window.indexedDB.open(name, version);
|
const req = indexedDB.open(name, version);
|
||||||
req.onupgradeneeded = (ev) => {
|
req.onupgradeneeded = (ev) => {
|
||||||
const db = ev.target.result;
|
const db = ev.target.result;
|
||||||
const txn = ev.target.transaction;
|
const txn = ev.target.transaction;
|
||||||
|
@ -52,11 +82,11 @@ export function reqAsPromise(req) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
req.addEventListener("success", event => {
|
req.addEventListener("success", event => {
|
||||||
resolve(event.target.result);
|
resolve(event.target.result);
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
});
|
});
|
||||||
req.addEventListener("error", () => {
|
req.addEventListener("error", () => {
|
||||||
reject(new IDBRequestError(req));
|
reject(new IDBRequestError(req));
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,11 +95,11 @@ export function txnAsPromise(txn) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
txn.addEventListener("complete", () => {
|
txn.addEventListener("complete", () => {
|
||||||
resolve();
|
resolve();
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
});
|
});
|
||||||
txn.addEventListener("abort", () => {
|
txn.addEventListener("abort", () => {
|
||||||
reject(new IDBRequestError(txn));
|
reject(new IDBRequestError(txn));
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -79,23 +109,24 @@ export function iterateCursor(cursorRequest, processValue) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
cursorRequest.onerror = () => {
|
cursorRequest.onerror = () => {
|
||||||
reject(new IDBRequestError(cursorRequest));
|
reject(new IDBRequestError(cursorRequest));
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
};
|
};
|
||||||
// collect results
|
// collect results
|
||||||
cursorRequest.onsuccess = (event) => {
|
cursorRequest.onsuccess = (event) => {
|
||||||
const cursor = event.target.result;
|
const cursor = event.target.result;
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
return; // end of results
|
return; // end of results
|
||||||
}
|
}
|
||||||
const result = processValue(cursor.value, cursor.key);
|
const result = processValue(cursor.value, cursor.key);
|
||||||
|
// TODO: don't use object for result and assume it's jumpTo when not === true/false or undefined
|
||||||
const done = result?.done;
|
const done = result?.done;
|
||||||
const jumpTo = result?.jumpTo;
|
const jumpTo = result?.jumpTo;
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
Promise._flush && Promise._flush();
|
needsSyncPromise && Promise._flush && Promise._flush();
|
||||||
} else if(jumpTo) {
|
} else if(jumpTo) {
|
||||||
cursor.continue(jumpTo);
|
cursor.continue(jumpTo);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue