17
} from 'react-dom/src/shared/ReactDOMTypes';
18
19
// This module registers the host dispatcher so it needs to be imported
20
-// but it does not have any exports
21
-import './ReactDOMFlightServerHostDispatcher';
20
+// even if no exports are used.
21
+import {preload, preloadModule} from './ReactDOMFlightServerHostDispatcher';
22
+
23
+import {getCrossOriginString} from '../shared/crossOriginStrings';
24
25
// We use zero to represent the absence of an explicit precedence because it is
26
// small, smaller than how we encode undefined, and is unambiguous. We could use
64
return new Set();
65
}
66
65
-export opaque type FormatContext = null;
67
+const NO_SCOPE = /* */ 0b000000;
68
+const NOSCRIPT_SCOPE = /* */ 0b000001;
69
+const PICTURE_SCOPE = /* */ 0b000010;
70
+
71
+export opaque type FormatContext = number;
72
73
export function createRootFormatContext(): FormatContext {
68
- return null;
74
+ return NO_SCOPE;
75
+}
76
+
77
+function processImg(props: Object, formatContext: FormatContext): void {
78
+ // This should mirror the logic of pushImg in ReactFizzConfigDOM.
79
+ const pictureOrNoScriptTagInScope =
80
+ formatContext & (PICTURE_SCOPE | NOSCRIPT_SCOPE);
81
+ const {src, srcSet} = props;
82
+ if (
83
+ props.loading !== 'lazy' &&
84
+ (src || srcSet) &&
85
+ (typeof src === 'string' || src == null) &&
86
+ (typeof srcSet === 'string' || srcSet == null) &&
87
+ props.fetchPriority !== 'low' &&
88
+ !pictureOrNoScriptTagInScope &&
89
+ // We exclude data URIs in src and srcSet since these should not be preloaded
90
+ !(
91
+ typeof src === 'string' &&
92
+ src[4] === ':' &&
93
+ (src[0] === 'd' || src[0] === 'D') &&
94
+ (src[1] === 'a' || src[1] === 'A') &&
95
+ (src[2] === 't' || src[2] === 'T') &&
96
+ (src[3] === 'a' || src[3] === 'A')
97
+ ) &&
98
+ !(
99
+ typeof srcSet === 'string' &&
100
+ srcSet[4] === ':' &&
101
+ (srcSet[0] === 'd' || srcSet[0] === 'D') &&
102
+ (srcSet[1] === 'a' || srcSet[1] === 'A') &&
103
+ (srcSet[2] === 't' || srcSet[2] === 'T') &&
104
+ (srcSet[3] === 'a' || srcSet[3] === 'A')
105
+ )
106
+ ) {
107
+ // We have a suspensey image and ought to preload it to optimize the loading of display blocking
108
+ // resumableState.
109
+ const sizes = typeof props.sizes === 'string' ? props.sizes : undefined;
110
+
111
+ const crossOrigin = getCrossOriginString(props.crossOrigin);
112
+
113
+ preload(
114
+ // The preload() API requires a href but if we have an imageSrcSet then that will take precedence.
115
+ // We already remove the href anyway in both Fizz and Fiber due to a Safari bug so the empty string
116
+ // will never actually appear in the DOM.
117
+ src || '',
118
+ 'image',
119
+ {
120
+ imageSrcSet: srcSet,
121
+ imageSizes: sizes,
122
+ crossOrigin: crossOrigin,
123
+ integrity: props.integrity,
124
+ type: props.type,
125
+ fetchPriority: props.fetchPriority,
126
+ referrerPolicy: props.referrerPolicy,
127
+ },
128
+ );
129
+ }
130
+}
131
+
132
+function processLink(props: Object, formatContext: FormatContext): void {
133
+ const noscriptTagInScope = formatContext & NOSCRIPT_SCOPE;
134
+ const rel = props.rel;
135
+ const href = props.href;
136
+ if (
137
+ noscriptTagInScope ||
138
+ props.itemProp != null ||
139
+ typeof rel !== 'string' ||
140
+ typeof href !== 'string' ||
141
+ href === ''
142
+ ) {
143
+ // We shouldn't preload resources that are in noscript or have no configuration.
144
+ return;
145
+ }
146
+
147
+ switch (rel) {
148
+ case 'preload': {
149
+ preload(href, props.as, {
150
+ crossOrigin: props.crossOrigin,
151
+ integrity: props.integrity,
152
+ nonce: props.nonce,
153
+ type: props.type,
154
+ fetchPriority: props.fetchPriority,
155
+ referrerPolicy: props.referrerPolicy,
156
+ imageSrcSet: props.imageSrcSet,
157
+ imageSizes: props.imageSizes,
158
+ media: props.media,
159
+ });
160
+ return;
161
+ }
162
+ case 'modulepreload': {
163
+ preloadModule(href, {
164
+ as: props.as,
165
+ crossOrigin: props.crossOrigin,
166
+ integrity: props.integrity,
167
+ nonce: props.nonce,
168
+ });
169
+ return;
170
+ }
171
+ case 'stylesheet': {
172
+ preload(href, 'stylesheet', {
173
+ crossOrigin: props.crossOrigin,
174
+ integrity: props.integrity,
175
+ nonce: props.nonce,
176
+ type: props.type,
177
+ fetchPriority: props.fetchPriority,
178
+ referrerPolicy: props.referrerPolicy,
179
+ media: props.media,
180
+ });
181
+ return;
182
+ }
183
+ }
184
}
185
186
export function getChildFormatContext(
188
type: string,
189
props: Object,
190
): FormatContext {
76
- return parentContext;
191
+ switch (type) {
192
+ case 'img':
193
+ processImg(props, parentContext);
194
+ return parentContext;
195
+ case 'link':
196
+ processLink(props, parentContext);
197
+ return parentContext;
198
+ case 'picture':
199
+ return parentContext | PICTURE_SCOPE;
200
+ case 'noscript':
201
+ return parentContext | NOSCRIPT_SCOPE;
202
+ default:
203
+ return parentContext;
204
+ }
205
}