1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #ifndef nsHtml5SpeculativeLoad_h
6 #define nsHtml5SpeculativeLoad_h
7 
8 #include "nsString.h"
9 #include "nsContentUtils.h"
10 #include "nsHtml5DocumentMode.h"
11 #include "nsHtml5String.h"
12 #include "ReferrerInfo.h"
13 
14 class nsHtml5TreeOpExecutor;
15 
16 enum eHtml5SpeculativeLoad {
17   eSpeculativeLoadUninitialized,
18   eSpeculativeLoadBase,
19   eSpeculativeLoadCSP,
20   eSpeculativeLoadMetaReferrer,
21   eSpeculativeLoadImage,
22   eSpeculativeLoadOpenPicture,
23   eSpeculativeLoadEndPicture,
24   eSpeculativeLoadPictureSource,
25   eSpeculativeLoadScript,
26   eSpeculativeLoadScriptFromHead,
27   eSpeculativeLoadNoModuleScript,
28   eSpeculativeLoadNoModuleScriptFromHead,
29   eSpeculativeLoadStyle,
30   eSpeculativeLoadManifest,
31   eSpeculativeLoadSetDocumentCharset,
32   eSpeculativeLoadSetDocumentMode,
33   eSpeculativeLoadPreconnect,
34   eSpeculativeLoadFont,
35   eSpeculativeLoadFetch,
36   eSpeculativeLoadMaybeComplainAboutCharset
37 };
38 
39 class nsHtml5SpeculativeLoad {
40   using Encoding = mozilla::Encoding;
41   template <typename T>
42   using NotNull = mozilla::NotNull<T>;
43 
44  public:
45   nsHtml5SpeculativeLoad();
46   ~nsHtml5SpeculativeLoad();
47 
InitBase(nsHtml5String aUrl)48   inline void InitBase(nsHtml5String aUrl) {
49     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
50                "Trying to reinitialize a speculative load!");
51     mOpCode = eSpeculativeLoadBase;
52     aUrl.ToString(mUrlOrSizes);
53   }
54 
InitMetaCSP(nsHtml5String aCSP)55   inline void InitMetaCSP(nsHtml5String aCSP) {
56     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
57                "Trying to reinitialize a speculative load!");
58     mOpCode = eSpeculativeLoadCSP;
59     nsString csp;  // Not Auto, because using it to hold nsStringBuffer*
60     aCSP.ToString(csp);
61     mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.Assign(
62         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(csp));
63   }
64 
InitMetaReferrerPolicy(nsHtml5String aReferrerPolicy)65   inline void InitMetaReferrerPolicy(nsHtml5String aReferrerPolicy) {
66     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
67                "Trying to reinitialize a speculative load!");
68     mOpCode = eSpeculativeLoadMetaReferrer;
69     nsString
70         referrerPolicy;  // Not Auto, because using it to hold nsStringBuffer*
71     aReferrerPolicy.ToString(referrerPolicy);
72     mReferrerPolicyOrIntegrity.Assign(
73         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
74             referrerPolicy));
75   }
76 
InitImage(nsHtml5String aUrl,nsHtml5String aCrossOrigin,nsHtml5String aMedia,nsHtml5String aReferrerPolicy,nsHtml5String aSrcset,nsHtml5String aSizes,bool aLinkPreload)77   inline void InitImage(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
78                         nsHtml5String aMedia, nsHtml5String aReferrerPolicy,
79                         nsHtml5String aSrcset, nsHtml5String aSizes,
80                         bool aLinkPreload) {
81     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
82                "Trying to reinitialize a speculative load!");
83     mOpCode = eSpeculativeLoadImage;
84     aUrl.ToString(mUrlOrSizes);
85     aCrossOrigin.ToString(mCrossOrigin);
86     aMedia.ToString(mMedia);
87     nsString
88         referrerPolicy;  // Not Auto, because using it to hold nsStringBuffer*
89     aReferrerPolicy.ToString(referrerPolicy);
90     mReferrerPolicyOrIntegrity.Assign(
91         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
92             referrerPolicy));
93     aSrcset.ToString(mCharsetOrSrcset);
94     aSizes.ToString(
95         mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
96     mIsLinkPreload = aLinkPreload;
97     mInitTimestamp = mozilla::TimeStamp::Now();
98   }
99 
InitFont(nsHtml5String aUrl,nsHtml5String aCrossOrigin,nsHtml5String aMedia,nsHtml5String aReferrerPolicy)100   inline void InitFont(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
101                        nsHtml5String aMedia, nsHtml5String aReferrerPolicy) {
102     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
103                "Trying to reinitialize a speculative load!");
104     mOpCode = eSpeculativeLoadFont;
105     aUrl.ToString(mUrlOrSizes);
106     aCrossOrigin.ToString(mCrossOrigin);
107     aMedia.ToString(mMedia);
108     nsString
109         referrerPolicy;  // Not Auto, because using it to hold nsStringBuffer*
110     aReferrerPolicy.ToString(referrerPolicy);
111     mReferrerPolicyOrIntegrity.Assign(
112         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
113             referrerPolicy));
114     // This can be only triggered by <link rel=preload type=font>
115     mIsLinkPreload = true;
116   }
117 
InitFetch(nsHtml5String aUrl,nsHtml5String aCrossOrigin,nsHtml5String aMedia,nsHtml5String aReferrerPolicy)118   inline void InitFetch(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
119                         nsHtml5String aMedia, nsHtml5String aReferrerPolicy) {
120     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
121                "Trying to reinitialize a speculative load!");
122     mOpCode = eSpeculativeLoadFetch;
123     aUrl.ToString(mUrlOrSizes);
124     aCrossOrigin.ToString(mCrossOrigin);
125     aMedia.ToString(mMedia);
126     nsString
127         referrerPolicy;  // Not Auto, because using it to hold nsStringBuffer*
128     aReferrerPolicy.ToString(referrerPolicy);
129     mReferrerPolicyOrIntegrity.Assign(
130         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
131             referrerPolicy));
132 
133     // This method can be only be triggered by <link rel=preload type=fetch>,
134     // hence this operation is always a preload.
135     mIsLinkPreload = true;
136   }
137 
138   // <picture> elements have multiple <source> nodes followed by an <img>,
139   // where we use the first valid source, which may be the img. Because we
140   // can't determine validity at this point without parsing CSS and getting
141   // main thread state, we push preload operations for picture pushed and
142   // popped, so that the target of the preload ops can determine what picture
143   // and nesting level each source/img from the main preloading code exists
144   // at.
InitOpenPicture()145   inline void InitOpenPicture() {
146     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
147                "Trying to reinitialize a speculative load!");
148     mOpCode = eSpeculativeLoadOpenPicture;
149   }
150 
InitEndPicture()151   inline void InitEndPicture() {
152     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
153                "Trying to reinitialize a speculative load!");
154     mOpCode = eSpeculativeLoadEndPicture;
155   }
156 
InitPictureSource(nsHtml5String aSrcset,nsHtml5String aSizes,nsHtml5String aType,nsHtml5String aMedia)157   inline void InitPictureSource(nsHtml5String aSrcset, nsHtml5String aSizes,
158                                 nsHtml5String aType, nsHtml5String aMedia) {
159     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
160                "Trying to reinitialize a speculative load!");
161     mOpCode = eSpeculativeLoadPictureSource;
162     aSrcset.ToString(mCharsetOrSrcset);
163     aSizes.ToString(mUrlOrSizes);
164     aType.ToString(
165         mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
166     aMedia.ToString(mMedia);
167   }
168 
InitScript(nsHtml5String aUrl,nsHtml5String aCharset,nsHtml5String aType,nsHtml5String aCrossOrigin,nsHtml5String aMedia,nsHtml5String aIntegrity,nsHtml5String aReferrerPolicy,bool aParserInHead,bool aAsync,bool aDefer,bool aNoModule,bool aLinkPreload)169   inline void InitScript(nsHtml5String aUrl, nsHtml5String aCharset,
170                          nsHtml5String aType, nsHtml5String aCrossOrigin,
171                          nsHtml5String aMedia, nsHtml5String aIntegrity,
172                          nsHtml5String aReferrerPolicy, bool aParserInHead,
173                          bool aAsync, bool aDefer, bool aNoModule,
174                          bool aLinkPreload) {
175     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
176                "Trying to reinitialize a speculative load!");
177     if (aNoModule) {
178       mOpCode = aParserInHead ? eSpeculativeLoadNoModuleScriptFromHead
179                               : eSpeculativeLoadNoModuleScript;
180     } else {
181       mOpCode = aParserInHead ? eSpeculativeLoadScriptFromHead
182                               : eSpeculativeLoadScript;
183     }
184     aUrl.ToString(mUrlOrSizes);
185     aCharset.ToString(mCharsetOrSrcset);
186     aType.ToString(
187         mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
188     aCrossOrigin.ToString(mCrossOrigin);
189     aMedia.ToString(mMedia);
190     aIntegrity.ToString(mReferrerPolicyOrIntegrity);
191     nsAutoString referrerPolicy;
192     aReferrerPolicy.ToString(referrerPolicy);
193     referrerPolicy =
194         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
195             referrerPolicy);
196     mScriptReferrerPolicy =
197         mozilla::dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
198             referrerPolicy);
199 
200     mIsAsync = aAsync;
201     mIsDefer = aDefer;
202     mIsLinkPreload = aLinkPreload;
203   }
204 
InitImportStyle(nsString && aUrl)205   inline void InitImportStyle(nsString&& aUrl) {
206     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
207                "Trying to reinitialize a speculative load!");
208     mOpCode = eSpeculativeLoadStyle;
209     mUrlOrSizes = std::move(aUrl);
210     mCharsetOrSrcset.SetIsVoid(true);
211     mCrossOrigin.SetIsVoid(true);
212     mMedia.SetIsVoid(true);
213     mReferrerPolicyOrIntegrity.SetIsVoid(true);
214     mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.SetIsVoid(
215         true);
216   }
217 
InitStyle(nsHtml5String aUrl,nsHtml5String aCharset,nsHtml5String aCrossOrigin,nsHtml5String aMedia,nsHtml5String aReferrerPolicy,nsHtml5String aIntegrity,bool aLinkPreload)218   inline void InitStyle(nsHtml5String aUrl, nsHtml5String aCharset,
219                         nsHtml5String aCrossOrigin, nsHtml5String aMedia,
220                         nsHtml5String aReferrerPolicy, nsHtml5String aIntegrity,
221                         bool aLinkPreload) {
222     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
223                "Trying to reinitialize a speculative load!");
224     mOpCode = eSpeculativeLoadStyle;
225     aUrl.ToString(mUrlOrSizes);
226     aCharset.ToString(mCharsetOrSrcset);
227     aCrossOrigin.ToString(mCrossOrigin);
228     aMedia.ToString(mMedia);
229     nsString
230         referrerPolicy;  // Not Auto, because using it to hold nsStringBuffer*
231     aReferrerPolicy.ToString(referrerPolicy);
232     mReferrerPolicyOrIntegrity.Assign(
233         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
234             referrerPolicy));
235     aIntegrity.ToString(
236         mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
237     mIsLinkPreload = aLinkPreload;
238   }
239 
240   /**
241    * "Speculative" manifest loads aren't truly speculative--if a manifest
242    * gets loaded, we are committed to it. There can never be a <script>
243    * before the manifest, so the situation of having to undo a manifest due
244    * to document.write() never arises. The reason why a parser
245    * thread-discovered manifest gets loaded via the speculative load queue
246    * as opposed to tree operation queue is that the manifest must get
247    * processed before any actual speculative loads such as scripts. Thus,
248    * manifests seen by the parser thread have to maintain the queue order
249    * relative to true speculative loads. See bug 541079.
250    */
InitManifest(nsHtml5String aUrl)251   inline void InitManifest(nsHtml5String aUrl) {
252     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
253                "Trying to reinitialize a speculative load!");
254     mOpCode = eSpeculativeLoadManifest;
255     aUrl.ToString(mUrlOrSizes);
256   }
257 
258   /**
259    * We communicate the encoding change via the speculative operation
260    * queue in order to act upon it as soon as possible and so as not to
261    * have speculative loads generated after an encoding change fail to
262    * make use of the encoding change.
263    */
InitSetDocumentCharset(NotNull<const Encoding * > aEncoding,int32_t aCharsetSource,bool aCommitEncodingSpeculation)264   inline void InitSetDocumentCharset(NotNull<const Encoding*> aEncoding,
265                                      int32_t aCharsetSource,
266                                      bool aCommitEncodingSpeculation) {
267     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
268                "Trying to reinitialize a speculative load!");
269     mOpCode = eSpeculativeLoadSetDocumentCharset;
270     mCharsetOrSrcset.~nsString();
271     mEncoding = aEncoding;
272     mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.Assign(
273         (char16_t)aCharsetSource);
274     mCommitEncodingSpeculation = aCommitEncodingSpeculation;
275   }
276 
InitMaybeComplainAboutCharset(const char * aMsgId,bool aError,int32_t aLineNumber)277   inline void InitMaybeComplainAboutCharset(const char* aMsgId, bool aError,
278                                             int32_t aLineNumber) {
279     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
280                "Trying to reinitialize a speculative load!");
281     mOpCode = eSpeculativeLoadMaybeComplainAboutCharset;
282     mCharsetOrSrcset.~nsString();
283     mMsgId = aMsgId;
284     mIsError = aError;
285     // Transport a 32-bit integer as two 16-bit code units of a string
286     // in order to avoid adding an integer field to the object.
287     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1733043 for a better
288     // eventual approach.
289     char16_t high = (char16_t)(((uint32_t)aLineNumber) >> 16);
290     char16_t low = (char16_t)(((uint32_t)aLineNumber) & 0xFFFF);
291     mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.Assign(high);
292     mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.Append(low);
293   }
294 
295   /**
296    * Speculative document mode setting isn't really speculative. Once it
297    * happens, we are committed to it. However, this information needs to
298    * travel in the speculation queue in order to have this information
299    * available before parsing the speculatively loaded style sheets.
300    */
InitSetDocumentMode(nsHtml5DocumentMode aMode)301   inline void InitSetDocumentMode(nsHtml5DocumentMode aMode) {
302     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
303                "Trying to reinitialize a speculative load!");
304     mOpCode = eSpeculativeLoadSetDocumentMode;
305     mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.Assign(
306         (char16_t)aMode);
307   }
308 
InitPreconnect(nsHtml5String aUrl,nsHtml5String aCrossOrigin)309   inline void InitPreconnect(nsHtml5String aUrl, nsHtml5String aCrossOrigin) {
310     MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
311                "Trying to reinitialize a speculative load!");
312     mOpCode = eSpeculativeLoadPreconnect;
313     aUrl.ToString(mUrlOrSizes);
314     aCrossOrigin.ToString(mCrossOrigin);
315   }
316 
317   void Perform(nsHtml5TreeOpExecutor* aExecutor);
318 
319  private:
320   nsHtml5SpeculativeLoad(const nsHtml5SpeculativeLoad&) = delete;
321   nsHtml5SpeculativeLoad& operator=(const nsHtml5SpeculativeLoad&) = delete;
322 
323   eHtml5SpeculativeLoad mOpCode;
324 
325   /**
326    * Whether the refering element has async attribute.
327    */
328   bool mIsAsync;
329 
330   /**
331    * Whether the refering element has defer attribute.
332    */
333   bool mIsDefer;
334 
335   /**
336    * True if and only if this is a speculative load initiated by <link
337    * rel="preload"> tag encounter.  Passed to the handling loader as an
338    * indication to raise the priority.
339    */
340   bool mIsLinkPreload;
341 
342   /**
343    * Whether the charset complaint is an error.
344    */
345   bool mIsError;
346 
347   /**
348    * Whether setting document encoding involves also committing to an encoding
349    * speculation.
350    */
351   bool mCommitEncodingSpeculation;
352 
353   /* If mOpCode is eSpeculativeLoadPictureSource, this is the value of the
354    * "sizes" attribute. If the attribute is not set, this will be a void
355    * string. Otherwise it empty or the value of the url.
356    */
357   nsString mUrlOrSizes;
358   /**
359    * If mOpCode is eSpeculativeLoadScript[FromHead], this is the value of the
360    * "integrity" attribute. If the attribute is not set, this will be a void
361    * string. Otherwise it is empty or the value of the referrer policy.
362    */
363   nsString mReferrerPolicyOrIntegrity;
364   /**
365    * If mOpCode is eSpeculativeLoadStyle or eSpeculativeLoadScript[FromHead]
366    * then this is the value of the "charset" attribute. For
367    * eSpeculativeLoadSetDocumentCharset it is the charset that the
368    * document's charset is being set to. If mOpCode is eSpeculativeLoadImage
369    * or eSpeculativeLoadPictureSource, this is the value of the "srcset"
370    * attribute. If the attribute is not set, this will be a void string.
371    * Otherwise it's empty.
372    * For eSpeculativeLoadMaybeComplainAboutCharset mMsgId is used.
373    */
374   union {
375     nsString mCharsetOrSrcset;
376     const Encoding* mEncoding;
377     const char* mMsgId;
378   };
379   /**
380    * If mOpCode is eSpeculativeLoadSetDocumentCharset, this is a
381    * one-character string whose single character's code point is to be
382    * interpreted as a charset source integer. If mOpCode is
383    * eSpeculativeLoadSetDocumentMode, this is a one-character string whose
384    * single character's code point is to be interpreted as an
385    * nsHtml5DocumentMode. If mOpCode is eSpeculativeLoadCSP, this is a meta
386    * element's CSP value. If mOpCode is eSpeculativeLoadImage, this is the
387    * value of the "sizes" attribute. If the attribute is not set, this will
388    * be a void string. If mOpCode is eSpeculativeLoadStyle, this
389    * is the value of the "integrity" attribute. If the attribute is not set,
390    * this will be a void string. Otherwise it is empty or the value of the
391    * referrer policy. Otherwise, it is empty or the value of the type attribute.
392    */
393   nsString mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity;
394   /**
395    * If mOpCode is eSpeculativeLoadImage or eSpeculativeLoadScript[FromHead]
396    * or eSpeculativeLoadPreconnect or eSpeculativeLoadStyle this is the value of
397    * the "crossorigin" attribute.  If the attribute is not set, this will be a
398    * void string.
399    */
400   nsString mCrossOrigin;
401   /**
402    * If mOpCode is eSpeculativeLoadPictureSource or eSpeculativeLoadStyle or
403    * Fetch or Image or Media or Script this is the value of the relevant "media"
404    * attribute of the <link rel="preload"> or <link rel="stylesheet">. If the
405    * attribute is not set, or the preload didn't originate from a <link>, this
406    * will be a void string.
407    */
408   nsString mMedia;
409   /**
410    * If mOpCode is eSpeculativeLoadScript[FromHead] this represents the value
411    * of the "referrerpolicy" attribute. This field holds one of the values
412    * (REFERRER_POLICY_*) defined in nsIHttpChannel.
413    */
414   mozilla::dom::ReferrerPolicy mScriptReferrerPolicy;
415 
416   mozilla::TimeStamp mInitTimestamp;
417 };
418 
419 #endif  // nsHtml5SpeculativeLoad_h
420