Loader.h 17.4 KB
Newer Older
1
2
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
4
5
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
8

/* loading of CSS style sheets using the network APIs */

9
10
#ifndef mozilla_css_Loader_h
#define mozilla_css_Loader_h
11

12
#include <tuple>
13
14
15
16
#include <utility>

#include "mozilla/Attributes.h"
#include "mozilla/CORSMode.h"
17
#include "mozilla/dom/LinkStyle.h"
18
19
#include "mozilla/MemoryReporting.h"
#include "mozilla/UniquePtr.h"
20
#include "nsCompatibility.h"
21
#include "nsCycleCollectionParticipant.h"
22
#include "nsStringFwd.h"
23
24
25
26
#include "nsTArray.h"
#include "nsTObserverArray.h"

class nsICSSLoaderObserver;
27
class nsIConsoleReportCollector;
28
class nsIContent;
29
class nsIPrincipal;
30

31
namespace mozilla {
32
33
34

class StyleSheet;

35
namespace dom {
36
class DocGroup;
37
class Element;
38
}  // namespace dom
39
40
41
42

namespace css {

class SheetLoadData;
43
class SheetCache;
44
class ImportRule;
45

46
47
48
49
/*********************
 * Style sheet reuse *
 *********************/

50
51
class MOZ_RAII LoaderReusableStyleSheets {
 public:
52
  LoaderReusableStyleSheets() = default;
53
54
55
56
57
58
59
60
61
62

  /**
   * Look for a reusable sheet (see AddReusableSheet) matching the
   * given URL.  If found, set aResult, remove the reused sheet from
   * the internal list, and return true.  If not found, return false;
   * in this case, aResult is not modified.
   *
   * @param aURL the url to match
   * @param aResult [out] the style sheet which can be reused
   */
63
  bool FindReusableStyleSheet(nsIURI* aURL, RefPtr<StyleSheet>& aResult);
64
65
66
67
68
69
70
71

  /**
   * Indicate that a certain style sheet is available for reuse if its
   * URI matches the URI of an @import.  Sheets should be added in the
   * opposite order in which they are intended to be reused.
   *
   * @param aSheet the sheet which can be reused
   */
72
  void AddReusableSheet(StyleSheet* aSheet) {
73
74
75
    mReusableSheets.AppendElement(aSheet);
  }

76
 private:
77
  LoaderReusableStyleSheets(const LoaderReusableStyleSheets&) = delete;
78
79
  LoaderReusableStyleSheets& operator=(const LoaderReusableStyleSheets&) =
      delete;
80
81

  // The sheets that can be reused.
82
  nsTArray<RefPtr<StyleSheet>> mReusableSheets;
83
84
};

85
class Loader final {
86
  using ReferrerPolicy = dom::ReferrerPolicy;
87

88
 public:
89
90
91
92
93
94
95
96
  using Completed = dom::LinkStyle::Completed;
  using HasAlternateRel = dom::LinkStyle::HasAlternateRel;
  using IsAlternate = dom::LinkStyle::IsAlternate;
  using IsInline = dom::LinkStyle::IsInline;
  using IsExplicitlyEnabled = dom::LinkStyle::IsExplicitlyEnabled;
  using MediaMatched = dom::LinkStyle::MediaMatched;
  using LoadSheetResult = dom::LinkStyle::Update;
  using SheetInfo = dom::LinkStyle::SheetInfo;
97

98
  Loader();
99
100
101
  // aDocGroup is used for dispatching SheetLoadData in PostLoadEvent(). It
  // can be null if you want to use this constructor, and there's no
  // document when the Loader is constructed.
102
103
  explicit Loader(dom::DocGroup*);
  explicit Loader(dom::Document*);
104
105
106

 private:
  // Private destructor, to discourage deletion outside of Release():
107
108
  ~Loader();

109
 public:
110
111
  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Loader)
  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Loader)
112

113
  void DropDocumentReference();  // notification that doc is going away
114

115
116
117
  void SetCompatibilityMode(nsCompatibility aCompatMode) {
    mCompatMode = aCompatMode;
  }
118
  nsCompatibility GetCompatibilityMode() { return mCompatMode; }
119
120
121
122

  // TODO(emilio): Is the complexity of this method and carrying the titles
  // around worth it? The alternate sheets will load anyhow eventually...
  void DocumentStyleSheetSetChanged();
123
124
125
126
127

  // XXXbz sort out what the deal is with events!  When should they fire?

  /**
   * Load an inline style sheet.  If a successful result is returned and
128
   * result.WillNotify() is true, then aObserver is guaranteed to be notified
129
   * asynchronously once the sheet is marked complete.  If an error is
130
131
132
   * returned, or if result.WillNotify() is false, aObserver will not be
   * notified.  In addition to parsing the sheet, this method will insert it
   * into the stylesheet list of this CSSLoader's document.
133
134
   * @param aObserver the observer to notify when the load completes.
   *        May be null.
135
136
   * @param aBuffer the stylesheet data
   * @param aLineNumber the line number at which the stylesheet data started.
137
   */
138
139
140
  Result<LoadSheetResult, nsresult> LoadInlineStyle(
      const SheetInfo&, const nsAString& aBuffer, uint32_t aLineNumber,
      nsICSSLoaderObserver* aObserver);
141
142
143
144

  /**
   * Load a linked (document) stylesheet.  If a successful result is returned,
   * aObserver is guaranteed to be notified asynchronously once the sheet is
145
146
147
148
   * loaded and marked complete, i.e., result.WillNotify() will always return
   * true.  If an error is returned, aObserver will not be notified.  In
   * addition to loading the sheet, this method will insert it into the
   * stylesheet list of this CSSLoader's document.
149
150
151
   * @param aObserver the observer to notify when the load completes.
   *                  May be null.
   */
152
153
  Result<LoadSheetResult, nsresult> LoadStyleLink(
      const SheetInfo&, nsICSSLoaderObserver* aObserver);
154
155
156
157
158
159
160
161
162
163
164
165

  /**
   * Load a child (@import-ed) style sheet.  In addition to loading the sheet,
   * this method will insert it into the child sheet list of aParentSheet.  If
   * there is no sheet currently being parsed and the child sheet is not
   * complete when this method returns, then when the child sheet becomes
   * complete aParentSheet will be QIed to nsICSSLoaderObserver and
   * asynchronously notified, just like for LoadStyleLink.  Note that if the
   * child sheet is already complete when this method returns, no
   * nsICSSLoaderObserver notification will be sent.
   *
   * @param aParentSheet the parent of this child sheet
166
   * @param aParentData the SheetLoadData corresponding to the load of the
167
168
   *                    parent sheet. May be null for @import rules inserted via
   *                    CSSOM.
169
170
   * @param aURL the URL of the child sheet
   * @param aMedia the already-parsed media list for the child sheet
171
172
   * @param aSavedSheets any saved style sheets which could be reused
   *              for this load
173
   */
174
  nsresult LoadChildSheet(StyleSheet& aParentSheet, SheetLoadData* aParentData,
175
                          nsIURI* aURL, dom::MediaList* aMedia,
176
                          LoaderReusableStyleSheets* aSavedSheets);
177

178
179
  enum class UseSystemPrincipal { No, Yes };

180
181
182
183
184
185
186
187
  /**
   * Synchronously load and return the stylesheet at aURL.  Any child sheets
   * will also be loaded synchronously.  Note that synchronous loads over some
   * protocols may involve spinning up a new event loop, so use of this method
   * does NOT guarantee not receiving any events before the sheet loads.  This
   * method can be used to load sheets not associated with a document.
   *
   * @param aURL the URL of the sheet to load
188
189
   * @param aParsingMode the mode in which to parse the sheet
   *        (see comments at enum SheetParsingMode, above).
190
191
192
193
194
195
196
197
198
199
200
   * @param aUseSystemPrincipal if true, give the resulting sheet the system
   * principal no matter where it's being loaded from.
   *
   * NOTE: At the moment, this method assumes the sheet will be UTF-8, but
   * ideally it would allow arbitrary encodings.  Callers should NOT depend on
   * non-UTF8 sheets being treated as UTF-8 by this method.
   *
   * NOTE: A successful return from this method doesn't indicate anything about
   * whether the data could be parsed as CSS and doesn't indicate anything
   * about the status of child sheets of the returned sheet.
   */
201
202
  Result<RefPtr<StyleSheet>, nsresult> LoadSheetSync(
      nsIURI*, SheetParsingMode = eAuthorSheetFeatures,
203
204
      UseSystemPrincipal = UseSystemPrincipal::No);

205
  enum class IsPreload : uint8_t {
206
207
208
209
210
211
212
213
    No,
    // This is a speculative load initiated by a <link rel=stylesheet> tag
    // scanned by the parser, or @import rules found in a <style> tag.
    FromParser,
    // This is a speculative load as well, but initiated by
    // <link rel="preload" as="style">
    FromLink,
  };
214

215
216
217
218
219
220
221
222
223
224
225
  /**
   * Asynchronously load the stylesheet at aURL.  If a successful result is
   * returned, aObserver is guaranteed to be notified asynchronously once the
   * sheet is loaded and marked complete.  This method can be used to load
   * sheets not associated with a document.
   *
   * @param aURL the URL of the sheet to load
   * @param aParsingMode the mode in which to parse the sheet
   *        (see comments at enum SheetParsingMode, above).
   * @param aUseSystemPrincipal if true, give the resulting sheet the system
   * principal no matter where it's being loaded from.
226
   * @param aReferrerInfo referrer information of the sheet.
227
228
   * @param aObserver the observer to notify when the load completes.
   *                  Must not be null.
229
230
   * @return the sheet to load. Note that the sheet may well not be loaded by
   * the time this method returns.
231
232
233
234
235
   *
   * NOTE: At the moment, this method assumes the sheet will be UTF-8, but
   * ideally it would allow arbitrary encodings.  Callers should NOT depend on
   * non-UTF8 sheets being treated as UTF-8 by this method.
   */
236
  Result<RefPtr<StyleSheet>, nsresult> LoadSheet(
237
238
239
      nsIURI* aURI, IsPreload, const Encoding* aPreloadEncoding,
      nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
      CORSMode = CORS_NONE, const nsAString& aIntegrity = EmptyString());
240

241
  /**
242
   * As above, but without caring for a couple things.
243
   */
244
  Result<RefPtr<StyleSheet>, nsresult> LoadSheet(nsIURI*, SheetParsingMode,
245
                                                 UseSystemPrincipal,
246
                                                 nsICSSLoaderObserver*);
247
248
249
250
251

  /**
   * Stop loading all sheets.  All nsICSSLoaderObservers involved will be
   * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
   */
252
  void Stop();
253
254

  /**
255
   * nsresult Loader::StopLoadingSheet(nsIURI* aURL), which notifies the
256
   * nsICSSLoaderObserver with NS_BINDING_ABORTED, was removed in Bug 556446.
257
   * It can be found in revision 2c44a32052ad.
258
259
260
261
262
263
264
265
266
   */

  /**
   * Whether the loader is enabled or not.
   * When disabled, processing of new styles is disabled and an attempt
   * to do so will fail with a return code of
   * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable
   * currently loading styles or already processed styles.
   */
267
268
  bool GetEnabled() { return mEnabled; }
  void SetEnabled(bool aEnabled) { mEnabled = aEnabled; }
269

270
271
272
  /**
   * Get the document we live for. May return null.
   */
273
  dom::Document* GetDocument() const { return mDocument; }
274

275
276
277
278
  /**
   * Return true if this loader has pending loads (ones that would send
   * notifications to an nsICSSLoaderObserver attached to this loader).
   * If called from inside nsICSSLoaderObserver::StyleSheetLoaded, this will
279
   * return false if and only if that is the last StyleSheetLoaded
280
281
   * notification the CSSLoader knows it's going to send.  In other words, if
   * two sheets load at once (via load coalescing, e.g.), HasPendingLoads()
282
   * will return true during notification for the first one, and false
283
284
   * during notification for the second one.
   */
285
  bool HasPendingLoads();
286
287
288
289
290
291
292
293
294
295

  /**
   * Add an observer to this loader.  The observer will be notified
   * for all loads that would have notified their own observers (even
   * if those loads don't have observers attached to them).
   * Load-specific observers will be notified before generic
   * observers.  The loader holds a reference to the observer.
   *
   * aObserver must not be null.
   */
296
  void AddObserver(nsICSSLoaderObserver* aObserver);
297
298
299
300
301
302
303
304
305

  /**
   * Remove an observer added via AddObserver.
   */
  void RemoveObserver(nsICSSLoaderObserver* aObserver);

  // These interfaces are public only for the benefit of static functions
  // within nsCSSLoader.cpp.

306
307
  // IsAlternateSheet can change our currently selected style set if none is
  // selected and aHasAlternateRel is false.
308
  IsAlternate IsAlternateSheet(const nsAString& aTitle, bool aHasAlternateRel);
309

310
  typedef nsTArray<RefPtr<SheetLoadData>> LoadDataArray;
311

312
  // Measure our size.
313
  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
314

315
 private:
316
  friend class SheetLoadData;
317
  friend class StreamLoader;
318

319
320
321
322
  // Helpers to conditionally block onload if mDocument is non-null.
  void BlockOnload();
  void UnblockOnload(bool aFireSync);

323
324
325
326
  // Helper to select the correct dispatch target for asynchronous events for
  // this loader.
  already_AddRefed<nsISerialEventTarget> DispatchTarget();

327
328
  nsresult CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
                              nsIPrincipal* aTriggeringPrincipal,
329
                              nsIURI* aTargetURI, nsINode* aRequestingNode,
330
                              const nsAString& aNonce, IsPreload);
331

332
333
334
335
336
337
338
339
  enum class SheetState : uint8_t {
    Unknown = 0,
    NeedsParser,
    Pending,
    Loading,
    Complete
  };

340
  std::tuple<RefPtr<StyleSheet>, SheetState> CreateSheet(
341
      const SheetInfo& aInfo, nsIPrincipal* aTriggeringPrincipal,
342
343
      css::SheetParsingMode aParsingMode, bool aSyncLoad,
      IsPreload aIsPreload) {
344
    return CreateSheet(aInfo.mURI, aInfo.mContent, aTriggeringPrincipal,
345
                       aParsingMode, aInfo.mCORSMode, aInfo.mReferrerInfo,
346
                       aInfo.mIntegrity, aSyncLoad, aIsPreload);
347
348
  }

349
350
351
  // For inline style, the aURI param is null, but the aLinkingContent
  // must be non-null then.  The loader principal must never be null
  // if aURI is not null.
352
  std::tuple<RefPtr<StyleSheet>, SheetState> CreateSheet(
353
354
355
356
      nsIURI* aURI, nsIContent* aLinkingContent,
      nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode, CORSMode,
      nsIReferrerInfo* aLoadingReferrerInfo, const nsAString& aIntegrity,
      bool aSyncLoad, IsPreload aIsPreload);
357

358
359
360
  // Pass in either a media string or the MediaList from the CSSParser.  Don't
  // pass both.
  //
361
362
  // This method will set the sheet's enabled state based on IsAlternate and co.
  MediaMatched PrepareSheet(StyleSheet&, const nsAString& aTitle,
363
364
                            const nsAString& aMediaString, dom::MediaList*,
                            IsAlternate, IsExplicitlyEnabled);
365

366
367
  // Inserts a style sheet in a document or a ShadowRoot.
  void InsertSheetInTree(StyleSheet& aSheet, nsIContent* aLinkingContent);
368
369
  // Inserts a style sheet into a parent style sheet.
  void InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet);
370

371
  Result<RefPtr<StyleSheet>, nsresult> InternalLoadNonDocumentSheet(
372
      nsIURI* aURL, IsPreload, SheetParsingMode aParsingMode,
373
374
375
      UseSystemPrincipal, const Encoding* aPreloadEncoding,
      nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
      CORSMode aCORSMode, const nsAString& aIntegrity);
376
377
378
379

  // Post a load event for aObserver to be notified about aSheet.  The
  // notification will be sent with status NS_OK unless the load event is
  // canceled at some point (in which case it will be sent with
380
381
  // NS_BINDING_ABORTED).
  nsresult PostLoadEvent(RefPtr<SheetLoadData>);
382
383

  // Start the loads of all the sheets in mPendingDatas
384
  void StartDeferredLoads();
385

386
  void HandleLoadEvent(SheetLoadData&);
387

388
389
  // Note: LoadSheet is responsible for setting the sheet to complete on
  // failure.
390
  nsresult LoadSheet(SheetLoadData&, SheetState);
391

392
  enum class AllowAsyncParse {
393
394
395
396
397
398
399
400
401
402
403
    Yes,
    No,
  };

  // Parse the stylesheet in the load data.
  //
  // Returns whether the parse finished. It may not finish e.g. if the sheet had
  // an @import.
  //
  // If this function returns Completed::Yes, then ParseSheet also called
  // SheetComplete on aLoadData.
404
  Completed ParseSheet(const nsACString&, SheetLoadData&, AllowAsyncParse);
405

406
407
408
  // The load of the sheet in the load data is done, one way or another.
  // Do final cleanup.
  void SheetComplete(SheetLoadData&, nsresult aStatus);
409
410
411
412

  // The guts of SheetComplete.  This may be called recursively on parent datas
  // or datas that had glommed on to a single load.  The array is there so load
  // datas whose observers need to be notified can be added to it.
413
  void DoSheetComplete(SheetLoadData&, LoadDataArray& aDatasToNotify);
414
415
416
417

  // Mark the given SheetLoadData, as well as any of its siblings, parents, etc
  // transitively, as failed.  The idea is to mark as failed any load that was
  // directly or indirectly @importing the sheet this SheetLoadData represents.
418
  void MarkLoadTreeFailed(SheetLoadData&);
419

420
  struct Sheets;
421
  UniquePtr<Sheets> mSheets;
422
423
424

  // The array of posted stylesheet loaded events (SheetLoadDatas) we have.
  // Note that these are rare.
425
  LoadDataArray mPostedEvents;
426
427

  // Our array of "global" observers
428
  nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>> mObservers;
429

430
431
  // This reference is nulled by the Document in it's destructor through
  // DropDocumentReference().
432
  dom::Document* MOZ_NON_OWNING_REF mDocument;  // the document we live for
433

434
  // For dispatching events via DocGroup::Dispatch() when mDocument is nullptr.
435
  RefPtr<dom::DocGroup> mDocGroup;
436
437
438
439
440

  // Number of datas still waiting to be notified on if we're notifying on a
  // whole bunch at once (e.g. in one of the stop methods).  This is used to
  // make sure that HasPendingLoads() won't return false until we're notifying
  // on the last data we're working with.
441
  uint32_t mDatasToNotifyOn;
442

443
  nsCompatibility mCompatMode;
444

445
  bool mEnabled;  // is enabled to load new styles
446

447
448
  nsCOMPtr<nsIConsoleReportCollector> mReporter;

449
#ifdef DEBUG
450
451
  // Whether we're in a necko callback atm.
  bool mSyncCallback = false;
452
453
454
#endif
};

455
456
}  // namespace css
}  // namespace mozilla
457

458
#endif /* mozilla_css_Loader_h */