Skip to content

Commit

Permalink
🚀 FID improvement use isInputPending if available for chunk. (#30300)
Browse files Browse the repository at this point in the history
* use isInputPending if available

* Add tests

* Address nit

* Closure doesnt understand navigator.scheduler yet

* unwind unwind

* Override type defintion for Navigator since its incomplete in Closure

* Address nit

* Tests renaming
  • Loading branch information
kristoferbaxter committed Sep 23, 2020
1 parent aa0804a commit 463c561
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 4 deletions.
15 changes: 12 additions & 3 deletions src/chunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ class Chunks {
this.boundExecute_ = this.execute_.bind(this);
/** @private {number} */
this.durationOfLastExecution_ = 0;
/** @private @const {boolean} */
this.supportsInputPending_ = !!(
this.win_.navigator.scheduling &&
this.win_.navigator.scheduling.isInputPending
);

/**
* Set to true if we scheduled a macro or micro task to execute the next
Expand Down Expand Up @@ -449,13 +454,17 @@ class Chunks {
* @private
*/
executeAsap_(idleDeadline) {
// If we've spent over 5 millseconds executing the
// last instruction yeild back to the main thread.
// If the user-agent supports isInputPending, use it to break to a macro task as necessary.
// Otherwise If we've spent over 5 millseconds executing the
// last instruction yield back to the main thread.
// 5 milliseconds is a magic number.
if (
!allowLongTasks &&
this.bodyIsVisible_ &&
this.durationOfLastExecution_ > 5
(this.supportsInputPending_
? /** @type {!{scheduling: {isInputPending: Function}}} */ (this.win_
.navigator).scheduling.isInputPending()
: this.durationOfLastExecution_ > 5)
) {
this.durationOfLastExecution_ = 0;
this.requestMacroTask_();
Expand Down
109 changes: 108 additions & 1 deletion test/unit/test-chunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ describe('long tasks', () => {
).macroAfterLongTask_ = true;
});

it('should not run macro tasks with invisible bodys', (done) => {
it('should not break out of microtask loop when body is invisible', (done) => {
startupChunk(env.win.document, complete('init', true));
startupChunk(env.win.document, complete('a', true));
startupChunk(env.win.document, complete('b', true));
Expand Down Expand Up @@ -482,6 +482,113 @@ describe('long tasks', () => {
);
});

describe('isInputPending usage', () => {
describes.fakeWin(
'pending input breaks microtask loop to subsequent macrotask',
{
amp: false,
},
(env) => {
let subscriptions;
let progress;
let postMessageCalls;
let pendingInput;

function complete(str, simulatePendingInputAfter) {
return function (unusedIdleDeadline) {
if (simulatePendingInputAfter) {
pendingInput = true;
}
progress += str;
};
}

function runSubs() {
subscriptions['message']
.slice()
.forEach((method) => method({data: 'amp-macro-task'}));
}

beforeEach(() => {
postMessageCalls = 0;
pendingInput = false;
subscriptions = {};

env.win.navigator.scheduling = {
isInputPending: function () {
if (pendingInput) {
pendingInput = false;
return true;
}

return false;
},
};

installDocService(env.win, /* isSingleDoc */ true);

env.win.addEventListener = function (type, handler) {
if (subscriptions[type] && !subscriptions[type].includes(handler)) {
subscriptions[type].push(handler);
} else {
subscriptions[type] = [handler];
}
};

env.win.postMessage = function (key) {
expect(key).to.equal('amp-macro-task');
postMessageCalls++;
runSubs();
};

progress = '';
chunkInstanceForTesting(
env.win.document.documentElement
).macroAfterLongTask_ = true;
});

it('should not break out of microtask loop when body is invisible', (done) => {
startupChunk(env.win.document, complete('init', true));
startupChunk(env.win.document, complete('a', true));
startupChunk(env.win.document, complete('b', true));
startupChunk(env.win.document, () => {
expect(progress).to.equal('initab');
done();
});
});

it('should execute chunks after pending input in a macro task', (done) => {
startupChunk(env.win.document, complete('1', true));
startupChunk(env.win.document, complete('2', false));
startupChunk(
env.win.document,
function () {
complete('3', false)();
expect(progress).to.equal('123');
expect(postMessageCalls).to.equal(0);
},
/* make body visible */ true
);
startupChunk(env.win.document, () => {
expect(postMessageCalls).to.equal(1);
expect(progress).to.equal('123');
complete('4', false)();
});
startupChunk(env.win.document, () => {
expect(postMessageCalls).to.equal(1);
expect(progress).to.equal('1234');
});
startupChunk(env.win.document, complete('5', true));
startupChunk(env.win.document, () => {
expect(postMessageCalls).to.equal(2);
expect(progress).to.equal('12345');
done();
});
});
}
);
});

describe('onIdle', () => {
let win;
let calls;
Expand Down

0 comments on commit 463c561

Please sign in to comment.