Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* loading of CSS style sheets using the network APIs */
8 #ifndef mozilla_css_Loader_h
9 #define mozilla_css_Loader_h
11 #include "nsIPrincipal.h"
12 #include "nsAString.h"
13 #include "nsAutoPtr.h"
14 #include "nsCompatibility.h"
15 #include "nsDataHashtable.h"
16 #include "nsInterfaceHashtable.h"
17 #include "nsRefPtrHashtable.h"
18 #include "nsTArray.h"
19 #include "nsTObserverArray.h"
20 #include "nsURIHashKey.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/CORSMode.h"
23 #include "mozilla/MemoryReporting.h"
25 class nsIAtom;
26 class nsICSSLoaderObserver;
27 class nsCSSStyleSheet;
28 class nsIContent;
29 class nsIDocument;
30 class nsCSSParser;
31 class nsMediaList;
32 class nsIStyleSheetLinkingElement;
33 class nsCycleCollectionTraversalCallback;
35 namespace mozilla {
36 namespace dom {
37 class Element;
38 }
39 }
41 namespace mozilla {
43 class URIPrincipalAndCORSModeHashKey : public nsURIHashKey
44 {
45 public:
46 typedef URIPrincipalAndCORSModeHashKey* KeyType;
47 typedef const URIPrincipalAndCORSModeHashKey* KeyTypePointer;
49 URIPrincipalAndCORSModeHashKey(const URIPrincipalAndCORSModeHashKey* aKey)
50 : nsURIHashKey(aKey->mKey), mPrincipal(aKey->mPrincipal),
51 mCORSMode(aKey->mCORSMode)
52 {
53 MOZ_COUNT_CTOR(URIPrincipalAndCORSModeHashKey);
54 }
55 URIPrincipalAndCORSModeHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
56 CORSMode aCORSMode)
57 : nsURIHashKey(aURI), mPrincipal(aPrincipal), mCORSMode(aCORSMode)
58 {
59 MOZ_COUNT_CTOR(URIPrincipalAndCORSModeHashKey);
60 }
61 URIPrincipalAndCORSModeHashKey(const URIPrincipalAndCORSModeHashKey& toCopy)
62 : nsURIHashKey(toCopy), mPrincipal(toCopy.mPrincipal),
63 mCORSMode(toCopy.mCORSMode)
64 {
65 MOZ_COUNT_CTOR(URIPrincipalAndCORSModeHashKey);
66 }
67 ~URIPrincipalAndCORSModeHashKey()
68 {
69 MOZ_COUNT_DTOR(URIPrincipalAndCORSModeHashKey);
70 }
72 URIPrincipalAndCORSModeHashKey* GetKey() const {
73 return const_cast<URIPrincipalAndCORSModeHashKey*>(this);
74 }
75 const URIPrincipalAndCORSModeHashKey* GetKeyPointer() const { return this; }
77 bool KeyEquals(const URIPrincipalAndCORSModeHashKey* aKey) const {
78 if (!nsURIHashKey::KeyEquals(aKey->mKey)) {
79 return false;
80 }
82 if (!mPrincipal != !aKey->mPrincipal) {
83 // One or the other has a principal, but not both... not equal
84 return false;
85 }
87 if (mCORSMode != aKey->mCORSMode) {
88 // Different CORS modes; we don't match
89 return false;
90 }
92 bool eq;
93 return !mPrincipal ||
94 (NS_SUCCEEDED(mPrincipal->Equals(aKey->mPrincipal, &eq)) && eq);
95 }
97 static const URIPrincipalAndCORSModeHashKey*
98 KeyToPointer(URIPrincipalAndCORSModeHashKey* aKey) { return aKey; }
99 static PLDHashNumber HashKey(const URIPrincipalAndCORSModeHashKey* aKey) {
100 return nsURIHashKey::HashKey(aKey->mKey);
101 }
103 nsIURI* GetURI() const { return nsURIHashKey::GetKey(); }
105 enum { ALLOW_MEMMOVE = true };
107 protected:
108 nsCOMPtr<nsIPrincipal> mPrincipal;
109 CORSMode mCORSMode;
110 };
114 namespace css {
116 class SheetLoadData;
117 class ImportRule;
119 /***********************************************************************
120 * Enum that describes the state of the sheet returned by CreateSheet. *
121 ***********************************************************************/
122 enum StyleSheetState {
123 eSheetStateUnknown = 0,
124 eSheetNeedsParser,
125 eSheetPending,
126 eSheetLoading,
127 eSheetComplete
128 };
130 class Loader MOZ_FINAL {
131 public:
132 Loader();
133 Loader(nsIDocument*);
135 private:
136 // Private destructor, to discourage deletion outside of Release():
137 ~Loader();
139 public:
140 NS_INLINE_DECL_REFCOUNTING(Loader)
142 void DropDocumentReference(); // notification that doc is going away
144 void SetCompatibilityMode(nsCompatibility aCompatMode)
145 { mCompatMode = aCompatMode; }
146 nsCompatibility GetCompatibilityMode() { return mCompatMode; }
147 nsresult SetPreferredSheet(const nsAString& aTitle);
149 // XXXbz sort out what the deal is with events! When should they fire?
151 /**
152 * Load an inline style sheet. If a successful result is returned and
153 * *aCompleted is false, then aObserver is guaranteed to be notified
154 * asynchronously once the sheet is marked complete. If an error is
155 * returned, or if *aCompleted is true, aObserver will not be notified. In
156 * addition to parsing the sheet, this method will insert it into the
157 * stylesheet list of this CSSLoader's document.
158 *
159 * @param aElement the element linking to the stylesheet. This must not be
160 * null and must implement nsIStyleSheetLinkingElement.
161 * @param aBuffer the stylesheet data
162 * @param aLineNumber the line number at which the stylesheet data started.
163 * @param aTitle the title of the sheet.
164 * @param aMedia the media string for the sheet.
165 * @param aObserver the observer to notify when the load completes.
166 * May be null.
167 * @param [out] aCompleted whether parsing of the sheet completed.
168 * @param [out] aIsAlternate whether the stylesheet ended up being an
169 * alternate sheet.
170 */
171 nsresult LoadInlineStyle(nsIContent* aElement,
172 const nsAString& aBuffer,
173 uint32_t aLineNumber,
174 const nsAString& aTitle,
175 const nsAString& aMedia,
176 mozilla::dom::Element* aScopeElement,
177 nsICSSLoaderObserver* aObserver,
178 bool* aCompleted,
179 bool* aIsAlternate);
181 /**
182 * Load a linked (document) stylesheet. If a successful result is returned,
183 * aObserver is guaranteed to be notified asynchronously once the sheet is
184 * loaded and marked complete. If an error is returned, aObserver will not
185 * be notified. In addition to loading the sheet, this method will insert it
186 * into the stylesheet list of this CSSLoader's document.
187 *
188 * @param aElement the element linking to the the stylesheet. May be null.
189 * @param aURL the URL of the sheet.
190 * @param aTitle the title of the sheet.
191 * @param aMedia the media string for the sheet.
192 * @param aHasAlternateRel whether the rel for this link included
193 * "alternate".
194 * @param aCORSMode the CORS mode for this load.
195 * @param aObserver the observer to notify when the load completes.
196 * May be null.
197 * @param [out] aIsAlternate whether the stylesheet actually ended up beinga
198 * an alternate sheet. Note that this need not match
199 * aHasAlternateRel.
200 */
201 nsresult LoadStyleLink(nsIContent* aElement,
202 nsIURI* aURL,
203 const nsAString& aTitle,
204 const nsAString& aMedia,
205 bool aHasAlternateRel,
206 CORSMode aCORSMode,
207 nsICSSLoaderObserver* aObserver,
208 bool* aIsAlternate);
210 /**
211 * Load a child (@import-ed) style sheet. In addition to loading the sheet,
212 * this method will insert it into the child sheet list of aParentSheet. If
213 * there is no sheet currently being parsed and the child sheet is not
214 * complete when this method returns, then when the child sheet becomes
215 * complete aParentSheet will be QIed to nsICSSLoaderObserver and
216 * asynchronously notified, just like for LoadStyleLink. Note that if the
217 * child sheet is already complete when this method returns, no
218 * nsICSSLoaderObserver notification will be sent.
219 *
220 * @param aParentSheet the parent of this child sheet
221 * @param aURL the URL of the child sheet
222 * @param aMedia the already-parsed media list for the child sheet
223 * @param aRule the @import rule importing this child. This is used to
224 * properly order the child sheet list of aParentSheet.
225 */
226 nsresult LoadChildSheet(nsCSSStyleSheet* aParentSheet,
227 nsIURI* aURL,
228 nsMediaList* aMedia,
229 ImportRule* aRule);
231 /**
232 * Synchronously load and return the stylesheet at aURL. Any child sheets
233 * will also be loaded synchronously. Note that synchronous loads over some
234 * protocols may involve spinning up a new event loop, so use of this method
235 * does NOT guarantee not receiving any events before the sheet loads. This
236 * method can be used to load sheets not associated with a document.
237 *
238 * @param aURL the URL of the sheet to load
239 * @param aEnableUnsafeRules whether unsafe rules are enabled for this
240 * sheet load
241 * Unsafe rules are rules that can violate key Gecko invariants if misused.
242 * In particular, most anonymous box pseudoelements must be very carefully
243 * styled or we will have severe problems. Therefore unsafe rules should
244 * never be enabled for stylesheets controlled by untrusted sites; preferably
245 * unsafe rules should only be enabled for agent sheets.
246 * @param aUseSystemPrincipal if true, give the resulting sheet the system
247 * principal no matter where it's being loaded from.
248 * @param [out] aSheet the loaded, complete sheet.
249 *
250 * NOTE: At the moment, this method assumes the sheet will be UTF-8, but
251 * ideally it would allow arbitrary encodings. Callers should NOT depend on
252 * non-UTF8 sheets being treated as UTF-8 by this method.
253 *
254 * NOTE: A successful return from this method doesn't indicate anything about
255 * whether the data could be parsed as CSS and doesn't indicate anything
256 * about the status of child sheets of the returned sheet.
257 */
258 nsresult LoadSheetSync(nsIURI* aURL, bool aEnableUnsafeRules,
259 bool aUseSystemPrincipal,
260 nsCSSStyleSheet** aSheet);
262 /**
263 * As above, but aUseSystemPrincipal and aEnableUnsafeRules are assumed false.
264 */
265 nsresult LoadSheetSync(nsIURI* aURL, nsCSSStyleSheet** aSheet) {
266 return LoadSheetSync(aURL, false, false, aSheet);
267 }
269 /**
270 * Asynchronously load the stylesheet at aURL. If a successful result is
271 * returned, aObserver is guaranteed to be notified asynchronously once the
272 * sheet is loaded and marked complete. This method can be used to load
273 * sheets not associated with a document.
274 *
275 * @param aURL the URL of the sheet to load
276 * @param aOriginPrincipal the principal to use for security checks. This
277 * can be null to indicate that these checks should
278 * be skipped.
279 * @param aCharset the encoding to use for converting the sheet data
280 * from bytes to Unicode. May be empty to indicate that the
281 * charset of the CSSLoader's document should be used. This
282 * is only used if neither the network transport nor the
283 * sheet itself indicate an encoding.
284 * @param aObserver the observer to notify when the load completes.
285 * Must not be null.
286 * @param [out] aSheet the sheet to load. Note that the sheet may well
287 * not be loaded by the time this method returns.
288 */
289 nsresult LoadSheet(nsIURI* aURL,
290 nsIPrincipal* aOriginPrincipal,
291 const nsCString& aCharset,
292 nsICSSLoaderObserver* aObserver,
293 nsCSSStyleSheet** aSheet);
295 /**
296 * Same as above, to be used when the caller doesn't care about the
297 * not-yet-loaded sheet.
298 */
299 nsresult LoadSheet(nsIURI* aURL,
300 nsIPrincipal* aOriginPrincipal,
301 const nsCString& aCharset,
302 nsICSSLoaderObserver* aObserver,
303 CORSMode aCORSMode = CORS_NONE);
305 /**
306 * Stop loading all sheets. All nsICSSLoaderObservers involved will be
307 * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
308 */
309 nsresult Stop(void);
311 /**
312 * nsresult Loader::StopLoadingSheet(nsIURI* aURL), which notifies the
313 * nsICSSLoaderObserver with NS_BINDING_ABORTED, was removed in Bug 556446.
314 * It can be found in revision 2c44a32052ad.
315 */
317 /**
318 * Whether the loader is enabled or not.
319 * When disabled, processing of new styles is disabled and an attempt
320 * to do so will fail with a return code of
321 * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable
322 * currently loading styles or already processed styles.
323 */
324 bool GetEnabled() { return mEnabled; }
325 void SetEnabled(bool aEnabled) { mEnabled = aEnabled; }
327 /**
328 * Get the document we live for. May return null.
329 */
330 nsIDocument* GetDocument() const { return mDocument; }
332 /**
333 * Return true if this loader has pending loads (ones that would send
334 * notifications to an nsICSSLoaderObserver attached to this loader).
335 * If called from inside nsICSSLoaderObserver::StyleSheetLoaded, this will
336 * return false if and only if that is the last StyleSheetLoaded
337 * notification the CSSLoader knows it's going to send. In other words, if
338 * two sheets load at once (via load coalescing, e.g.), HasPendingLoads()
339 * will return true during notification for the first one, and false
340 * during notification for the second one.
341 */
342 bool HasPendingLoads();
344 /**
345 * Add an observer to this loader. The observer will be notified
346 * for all loads that would have notified their own observers (even
347 * if those loads don't have observers attached to them).
348 * Load-specific observers will be notified before generic
349 * observers. The loader holds a reference to the observer.
350 *
351 * aObserver must not be null.
352 */
353 nsresult AddObserver(nsICSSLoaderObserver* aObserver);
355 /**
356 * Remove an observer added via AddObserver.
357 */
358 void RemoveObserver(nsICSSLoaderObserver* aObserver);
360 // These interfaces are public only for the benefit of static functions
361 // within nsCSSLoader.cpp.
363 // IsAlternate can change our currently selected style set if none
364 // is selected and aHasAlternateRel is false.
365 bool IsAlternate(const nsAString& aTitle, bool aHasAlternateRel);
367 typedef nsTArray<nsRefPtr<SheetLoadData> > LoadDataArray;
369 // Traverse the cached stylesheets we're holding on to. This should
370 // only be called from the document that owns this loader.
371 void TraverseCachedSheets(nsCycleCollectionTraversalCallback& cb);
373 // Unlink the cached stylesheets we're holding on to. Again, this
374 // should only be called from the document that owns this loader.
375 void UnlinkCachedSheets();
377 // Measure our size.
378 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
380 // Marks all the sheets at the given URI obsolete, and removes them from the
381 // cache.
382 nsresult ObsoleteSheet(nsIURI* aURI);
384 private:
385 friend class SheetLoadData;
387 static PLDHashOperator
388 RemoveEntriesWithURI(URIPrincipalAndCORSModeHashKey* aKey,
389 nsRefPtr<nsCSSStyleSheet> &aSheet,
390 void* aUserData);
392 // Note: null aSourcePrincipal indicates that the content policy and
393 // CheckLoadURI checks should be skipped.
394 nsresult CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
395 nsIURI* aTargetURI,
396 nsISupports* aContext);
399 // For inline style, the aURI param is null, but the aLinkingContent
400 // must be non-null then. The loader principal must never be null
401 // if aURI is not null.
402 // *aIsAlternate is set based on aTitle and aHasAlternateRel.
403 nsresult CreateSheet(nsIURI* aURI,
404 nsIContent* aLinkingContent,
405 nsIPrincipal* aLoaderPrincipal,
406 CORSMode aCORSMode,
407 bool aSyncLoad,
408 bool aHasAlternateRel,
409 const nsAString& aTitle,
410 StyleSheetState& aSheetState,
411 bool *aIsAlternate,
412 nsCSSStyleSheet** aSheet);
414 // Pass in either a media string or the nsMediaList from the
415 // CSSParser. Don't pass both.
416 // This method will set the sheet's enabled state based on isAlternate
417 void PrepareSheet(nsCSSStyleSheet* aSheet,
418 const nsAString& aTitle,
419 const nsAString& aMediaString,
420 nsMediaList* aMediaList,
421 dom::Element* aScopeElement,
422 bool isAlternate);
424 nsresult InsertSheetInDoc(nsCSSStyleSheet* aSheet,
425 nsIContent* aLinkingContent,
426 nsIDocument* aDocument);
428 nsresult InsertChildSheet(nsCSSStyleSheet* aSheet,
429 nsCSSStyleSheet* aParentSheet,
430 ImportRule* aParentRule);
432 nsresult InternalLoadNonDocumentSheet(nsIURI* aURL,
433 bool aAllowUnsafeRules,
434 bool aUseSystemPrincipal,
435 nsIPrincipal* aOriginPrincipal,
436 const nsCString& aCharset,
437 nsCSSStyleSheet** aSheet,
438 nsICSSLoaderObserver* aObserver,
439 CORSMode aCORSMode = CORS_NONE);
441 // Post a load event for aObserver to be notified about aSheet. The
442 // notification will be sent with status NS_OK unless the load event is
443 // canceled at some point (in which case it will be sent with
444 // NS_BINDING_ABORTED). aWasAlternate indicates the state when the load was
445 // initiated, not the state at some later time. aURI should be the URI the
446 // sheet was loaded from (may be null for inline sheets). aElement is the
447 // owning element for this sheet.
448 nsresult PostLoadEvent(nsIURI* aURI,
449 nsCSSStyleSheet* aSheet,
450 nsICSSLoaderObserver* aObserver,
451 bool aWasAlternate,
452 nsIStyleSheetLinkingElement* aElement);
454 // Start the loads of all the sheets in mPendingDatas
455 void StartAlternateLoads();
457 // Handle an event posted by PostLoadEvent
458 void HandleLoadEvent(SheetLoadData* aEvent);
460 // Note: LoadSheet is responsible for releasing aLoadData and setting the
461 // sheet to complete on failure.
462 nsresult LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState);
464 // Parse the stylesheet in aLoadData. The sheet data comes from aInput.
465 // Set aCompleted to true if the parse finished, false otherwise (e.g. if the
466 // sheet had an @import). If aCompleted is true when this returns, then
467 // ParseSheet also called SheetComplete on aLoadData.
468 nsresult ParseSheet(const nsAString& aInput,
469 SheetLoadData* aLoadData,
470 bool& aCompleted);
472 // The load of the sheet in aLoadData is done, one way or another. Do final
473 // cleanup, including releasing aLoadData.
474 void SheetComplete(SheetLoadData* aLoadData, nsresult aStatus);
476 // The guts of SheetComplete. This may be called recursively on parent datas
477 // or datas that had glommed on to a single load. The array is there so load
478 // datas whose observers need to be notified can be added to it.
479 void DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
480 LoadDataArray& aDatasToNotify);
482 struct Sheets {
483 nsRefPtrHashtable<URIPrincipalAndCORSModeHashKey, nsCSSStyleSheet>
484 mCompleteSheets;
485 nsDataHashtable<URIPrincipalAndCORSModeHashKey, SheetLoadData*>
486 mLoadingDatas; // weak refs
487 nsDataHashtable<URIPrincipalAndCORSModeHashKey, SheetLoadData*>
488 mPendingDatas; // weak refs
489 };
490 nsAutoPtr<Sheets> mSheets;
492 // We're not likely to have many levels of @import... But likely to have
493 // some. Allocate some storage, what the hell.
494 nsAutoTArray<SheetLoadData*, 8> mParsingDatas;
496 // The array of posted stylesheet loaded events (SheetLoadDatas) we have.
497 // Note that these are rare.
498 LoadDataArray mPostedEvents;
500 // Our array of "global" observers
501 // XXXbz these are strong refs; should we be cycle collecting CSS loaders?
502 nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> > mObservers;
504 // the load data needs access to the document...
505 nsIDocument* mDocument; // the document we live for
508 // Number of datas still waiting to be notified on if we're notifying on a
509 // whole bunch at once (e.g. in one of the stop methods). This is used to
510 // make sure that HasPendingLoads() won't return false until we're notifying
511 // on the last data we're working with.
512 uint32_t mDatasToNotifyOn;
514 nsCompatibility mCompatMode;
515 nsString mPreferredSheet; // title of preferred sheet
517 bool mEnabled; // is enabled to load new styles
519 #ifdef DEBUG
520 bool mSyncCallback;
521 #endif
522 };
524 } // namespace css
525 } // namespace mozilla
527 #endif /* mozilla_css_Loader_h */