5905
5906
type SuspendedState = {
5907
stylesheets: null | Map<StylesheetResource, HoistableRoot>,
5908
- count: number,
5908
+ count: number, // suspensey css and active view transitions
5909
+ imgCount: number, // suspensey images
5910
+ waitingForImages: boolean, // false when we're no longer blocking on images
5911
unsuspend: null | (() => void),
5912
};
5913
let suspendedState: null | SuspendedState = null;
5916
suspendedState = {
5917
stylesheets: null,
5918
count: 0,
5919
+ imgCount: 0,
5920
+ waitingForImages: true,
5921
// We use a noop function when we begin suspending because if possible we want the
5922
// waitfor step to finish synchronously. If it doesn't we'll return a function to
5923
// provide the actual unsuspend function and that will get completed when the count
5926
};
5927
}
5928
5929
+const SUSPENSEY_STYLESHEET_TIMEOUT = 60000;
5930
+
5931
const SUSPENSEY_IMAGE_TIMEOUT = 500;
5932
5933
export function suspendInstance(
5952
// If this browser supports decode() API, we use it to suspend waiting on the image.
5953
// The loading should have already started at this point, so it should be enough to
5954
// just call decode() which should also wait for the data to finish loading.
5949
- state.count++;
5950
- const ping = onUnsuspend.bind(state);
5951
- Promise.race([
5952
- // $FlowFixMe[prop-missing]
5953
- instance.decode(),
5954
- new Promise(resolve => setTimeout(resolve, SUSPENSEY_IMAGE_TIMEOUT)),
5955
- ]).then(ping, ping);
5955
+ state.imgCount++;
5956
+ const ping = onUnsuspendImg.bind(state);
5957
+ // $FlowFixMe[prop-missing]
5958
+ instance.decode().then(ping, ping);
5959
}
5960
}
5961
6070
activeViewTransition.finished.then(ping, ping);
6071
}
6072
6070
-export function waitForCommitToBeReady(): null | ((() => void) => () => void) {
6073
+export function waitForCommitToBeReady(
6074
+ timeoutOffset: number,
6075
+): null | ((() => void) => () => void) {
6076
if (suspendedState === null) {
6077
throw new Error(
6078
'Internal React Error: suspendedState null when it was expected to exists. Please report this as a React bug.',
6090
6091
// We need to check the count again because the inserted stylesheets may have led to new
6092
// tasks to wait on.
6088
- if (state.count > 0) {
6093
+ if (state.count > 0 || state.imgCount > 0) {
6094
return commit => {
6095
// We almost never want to show content before its styles have loaded. But
6096
// eventually we will give up and allow unstyled content. So this number is
6107
state.unsuspend = null;
6108
unsuspend();
6109
}
6105
- }, 60000); // one minute
6110
+ }, SUSPENSEY_STYLESHEET_TIMEOUT + timeoutOffset);
6111
+
6112
+ const imgTimer = setTimeout(() => {
6113
+ // We're no longer blocked on images. If CSS resolves after this we can commit.
6114
+ state.waitingForImages = false;
6115
+ if (state.count === 0) {
6116
+ if (state.stylesheets) {
6117
+ insertSuspendedStylesheets(state, state.stylesheets);
6118
+ }
6119
+ if (state.unsuspend) {
6120
+ const unsuspend = state.unsuspend;
6121
+ state.unsuspend = null;
6122
+ unsuspend();
6123
+ }
6124
+ }
6125
+ }, SUSPENSEY_IMAGE_TIMEOUT + timeoutOffset);
6126
6127
state.unsuspend = commit;
6128
6129
return () => {
6130
state.unsuspend = null;
6131
clearTimeout(stylesheetTimer);
6132
+ clearTimeout(imgTimer);
6133
};
6134
};
6135
}
6136
return null;
6137
}
6138
6118
-function onUnsuspend(this: SuspendedState) {
6119
- this.count--;
6120
- if (this.count === 0) {
6121
- if (this.stylesheets) {
6139
+function checkIfFullyUnsuspended(state: SuspendedState) {
6140
+ if (state.count === 0 && (state.imgCount === 0 || !state.waitingForImages)) {
6141
+ if (state.stylesheets) {
6142
// If we haven't actually inserted the stylesheets yet we need to do so now before starting the commit.
6143
// The reason we do this after everything else has finished is because we want to have all the stylesheets
6144
// load synchronously right before mutating. Ideally the new styles will cause a single recalc only on the
6145
// new tree. When we filled up stylesheets we only inlcuded stylesheets with matching media attributes so we
6146
// wait for them to load before actually continuing. We expect this to increase the count above zero
6127
- insertSuspendedStylesheets(this, this.stylesheets);
6128
- } else if (this.unsuspend) {
6129
- const unsuspend = this.unsuspend;
6130
- this.unsuspend = null;
6147
+ insertSuspendedStylesheets(state, state.stylesheets);
6148
+ } else if (state.unsuspend) {
6149
+ const unsuspend = state.unsuspend;
6150
+ state.unsuspend = null;
6151
unsuspend();
6152
}
6153
}
6154
}
6155
6156
+function onUnsuspend(this: SuspendedState) {
6157
+ this.count--;
6158
+ checkIfFullyUnsuspended(this);
6159
+}
6160
+
6161
+function onUnsuspendImg(this: SuspendedState) {
6162
+ this.imgCount--;
6163
+ checkIfFullyUnsuspended(this);
6164
+}
6165
+
6166
// We use a value that is type distinct from precedence to track which one is last.
6167
// This ensures there is no collision with user defined precedences. Normally we would
6168
// just track this in module scope but since the precedences are tracked per HoistableRoot