mirror of
https://github.com/zen-browser/desktop.git
synced 2026-03-29 11:51:51 +00:00
refactor: switch to use DeferredTask in scheduling logic, p=#12480, c=live-folders
This commit is contained in:
@@ -3,17 +3,13 @@
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
requestIdleCallback: "resource://gre/modules/Timer.sys.mjs",
|
||||
cancelIdleCallback: "resource://gre/modules/Timer.sys.mjs",
|
||||
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
|
||||
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
|
||||
NetworkHelper: "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
|
||||
});
|
||||
|
||||
export class nsZenLiveFolderProvider {
|
||||
#timerHandle = null;
|
||||
#idleCallbackHandle = null;
|
||||
#task = null;
|
||||
|
||||
constructor({ id, manager, state }) {
|
||||
this.id = id;
|
||||
@@ -30,56 +26,50 @@ export class nsZenLiveFolderProvider {
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
this.stop();
|
||||
const result = await this.#internalFetch();
|
||||
this.start();
|
||||
this.#task.disarm();
|
||||
const result = await this.#fetchLiveFolder();
|
||||
this.#task.arm();
|
||||
return result;
|
||||
}
|
||||
|
||||
start() {
|
||||
const now = Date.now();
|
||||
const lastFetched = this.state.lastFetched;
|
||||
start(checkDelay = true) {
|
||||
const interval = this.state.interval;
|
||||
|
||||
const timeSinceLast = now - lastFetched;
|
||||
let delay = interval - timeSinceLast;
|
||||
|
||||
if (delay <= 0) {
|
||||
delay = 0;
|
||||
if (this.#task) {
|
||||
this.#task.finalize();
|
||||
}
|
||||
|
||||
this.#scheduleNext(delay);
|
||||
if (checkDelay) {
|
||||
const now = Date.now();
|
||||
const lastFetched = this.state.lastFetched;
|
||||
|
||||
const timeSinceLast = now - lastFetched;
|
||||
let delay = interval - timeSinceLast;
|
||||
|
||||
if (delay <= 0) {
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
this.#task = new lazy.DeferredTask(async () => {
|
||||
await this.#fetchLiveFolder();
|
||||
this.start(false);
|
||||
}, delay);
|
||||
} else {
|
||||
this.#task = new lazy.DeferredTask(async () => {
|
||||
await this.#fetchLiveFolder();
|
||||
this.#task.arm();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
this.#task.arm();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.#timerHandle) {
|
||||
lazy.clearTimeout(this.#timerHandle);
|
||||
this.#timerHandle = null;
|
||||
}
|
||||
|
||||
if (this.#idleCallbackHandle) {
|
||||
lazy.cancelIdleCallback(this.#idleCallbackHandle);
|
||||
this.#idleCallbackHandle = null;
|
||||
if (this.#task) {
|
||||
this.#task.disarm();
|
||||
}
|
||||
}
|
||||
|
||||
#scheduleNext(delay) {
|
||||
if (this.#timerHandle) {
|
||||
lazy.clearTimeout(this.#timerHandle);
|
||||
}
|
||||
|
||||
this.#timerHandle = lazy.setTimeout(() => {
|
||||
const fetchWhenIdle = () => {
|
||||
this.#internalFetch();
|
||||
this.#idleCallbackHandle = null;
|
||||
};
|
||||
|
||||
this.#idleCallbackHandle = lazy.requestIdleCallback(fetchWhenIdle);
|
||||
this.#scheduleNext(this.state.interval);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
async #internalFetch() {
|
||||
async #fetchLiveFolder() {
|
||||
try {
|
||||
const items = await this.fetchItems();
|
||||
this.state.lastFetched = Date.now();
|
||||
@@ -131,7 +121,7 @@ export class nsZenLiveFolderProvider {
|
||||
this.manager.saveState();
|
||||
}
|
||||
|
||||
fetch(url, { maxContentLength = 5 * 1024 * 1024 } = {}) {
|
||||
fetch(url, { maxContentLength = 5 * 1024 * 1024, method = "GET", body = null } = {}) {
|
||||
const uri = lazy.NetUtil.newURI(url);
|
||||
// TODO: Support userContextId when fetching, it should be inherited from the folder's
|
||||
// current space context ID.
|
||||
@@ -164,6 +154,27 @@ export class nsZenLiveFolderProvider {
|
||||
)
|
||||
.QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
method = method.toUpperCase();
|
||||
if (method === "POST") {
|
||||
const uploadChannel = channel
|
||||
.QueryInterface(Ci.nsIHttpChannel)
|
||||
.QueryInterface(Ci.nsIUploadChannel2);
|
||||
|
||||
if (body === null) {
|
||||
body = "";
|
||||
} else if (typeof body !== "string") {
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
||||
Ci.nsIStringInputStream
|
||||
);
|
||||
|
||||
stream.setByteStringData(body);
|
||||
uploadChannel.explicitSetUploadStream(stream, "application/json", -1, method, false);
|
||||
}
|
||||
|
||||
channel.requestMethod = method;
|
||||
let httpStatus = null;
|
||||
let contentType = "";
|
||||
let headerCharset = null;
|
||||
|
||||
@@ -13,10 +13,29 @@ function sleep(ms) {
|
||||
}
|
||||
|
||||
describe("Zen Live Folder Scheduling", () => {
|
||||
const INTERVAL = 250;
|
||||
const INTERVAL_OFFSET = 50;
|
||||
|
||||
let instance;
|
||||
let sandbox;
|
||||
let mockManager;
|
||||
|
||||
function createInstance({ id, interval, lastFetched }) {
|
||||
instance = new nsZenLiveFolderProvider({
|
||||
id,
|
||||
manager: mockManager,
|
||||
state: {
|
||||
interval,
|
||||
lastFetched,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchStub = sandbox.stub(instance, "fetchItems").resolves(["item1"]);
|
||||
sandbox.stub(instance, "getMetadata").returns({});
|
||||
|
||||
return { fetchStub };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
mockManager = {
|
||||
@@ -33,51 +52,84 @@ describe("Zen Live Folder Scheduling", () => {
|
||||
});
|
||||
|
||||
it("should fetch correctly at an interval", async () => {
|
||||
const INTERVAL = 250;
|
||||
|
||||
instance = new nsZenLiveFolderProvider({
|
||||
const { fetchStub } = createInstance({
|
||||
id: "test-folder",
|
||||
manager: mockManager,
|
||||
state: {
|
||||
interval: INTERVAL,
|
||||
lastFetched: Date.now(),
|
||||
},
|
||||
interval: INTERVAL,
|
||||
lastFetched: Date.now(),
|
||||
});
|
||||
|
||||
const fetchStub = sandbox.stub(instance, "fetchItems").resolves(["item1"]);
|
||||
sandbox.stub(instance, "getMetadata").returns({});
|
||||
|
||||
instance.start();
|
||||
|
||||
sinon.assert.notCalled(fetchStub);
|
||||
await sleep(INTERVAL / 2);
|
||||
sinon.assert.notCalled(fetchStub);
|
||||
|
||||
await sleep(INTERVAL * 2);
|
||||
Assert.ok(fetchStub.callCount > 1, "Should have fetched more than once");
|
||||
const startSpy = sandbox.spy(instance, "start");
|
||||
|
||||
await sleep(INTERVAL + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 1, "Should have fetched once after the first interval");
|
||||
|
||||
await sleep(INTERVAL + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 2, "Should have fetched 2 times");
|
||||
Assert.deepEqual(
|
||||
startSpy.firstCall.args,
|
||||
[false],
|
||||
"Start should have been called once with false"
|
||||
);
|
||||
|
||||
await sleep(INTERVAL + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 3, "Should have fetched 3 times");
|
||||
Assert.equal(startSpy.callCount, 1, "Start should not been called");
|
||||
|
||||
sinon.assert.called(mockManager.saveState);
|
||||
sinon.assert.called(mockManager.onLiveFolderFetch);
|
||||
});
|
||||
|
||||
it("should fetch immediately if overdue", async () => {
|
||||
const INTERVAL = 500;
|
||||
|
||||
instance = new nsZenLiveFolderProvider({
|
||||
const { fetchStub } = createInstance({
|
||||
id: "test-folder-overdue",
|
||||
manager: mockManager,
|
||||
state: {
|
||||
interval: INTERVAL,
|
||||
lastFetched: Date.now() - 3600000,
|
||||
},
|
||||
interval: INTERVAL,
|
||||
lastFetched: Date.now() - 3600000,
|
||||
});
|
||||
|
||||
const fetchStub = sandbox.stub(instance, "fetchItems").resolves(["item1"]);
|
||||
sandbox.stub(instance, "getMetadata").returns({});
|
||||
|
||||
instance.start();
|
||||
|
||||
await sleep(20);
|
||||
await sleep(INTERVAL_OFFSET);
|
||||
sinon.assert.calledOnce(fetchStub);
|
||||
});
|
||||
|
||||
it("should fetch with correct offset", async () => {
|
||||
const { fetchStub } = createInstance({
|
||||
id: "test-folder-delay",
|
||||
interval: INTERVAL,
|
||||
lastFetched: Date.now() - INTERVAL / 2,
|
||||
});
|
||||
|
||||
instance.start();
|
||||
await sleep(INTERVAL / 2 + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 1, "Should have fetched once");
|
||||
|
||||
await sleep(INTERVAL + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 2, "Should have fetched once with normal interval");
|
||||
});
|
||||
|
||||
it("should re-start the timer if interval was changed", async () => {
|
||||
const { fetchStub } = createInstance({
|
||||
id: "test-folder-interval-change",
|
||||
interval: INTERVAL,
|
||||
lastFetched: Date.now(),
|
||||
});
|
||||
|
||||
instance.start();
|
||||
|
||||
sinon.assert.notCalled(fetchStub);
|
||||
await sleep(INTERVAL + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 1, "Should have fetched once after the first interval");
|
||||
|
||||
const NEW_INTERVAL = 500;
|
||||
instance.state.interval = NEW_INTERVAL;
|
||||
|
||||
instance.stop();
|
||||
instance.start();
|
||||
|
||||
await sleep(NEW_INTERVAL + INTERVAL_OFFSET);
|
||||
Assert.equal(fetchStub.callCount, 2, "Should have once after the new interval");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user