1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef mozilla_StyleSheet_h
8 #define mozilla_StyleSheet_h
9
10 #include "mozilla/css/SheetParsingMode.h"
11 #include "mozilla/dom/CSSStyleSheetBinding.h"
12 #include "mozilla/dom/SRIMetadata.h"
13 #include "mozilla/CORSMode.h"
14 #include "mozilla/MozPromise.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/ServoBindingTypes.h"
17 #include "mozilla/ServoUtils.h"
18 #include "mozilla/StyleSheetInfo.h"
19 #include "mozilla/URLExtraData.h"
20 #include "nsICSSLoaderObserver.h"
21 #include "nsWrapperCache.h"
22 #include "nsCompatibility.h"
23 #include "nsStringFwd.h"
24
25 class nsINode;
26 class nsIPrincipal;
27 struct RawServoSharedMemoryBuilder;
28 class nsIReferrerInfo;
29
30 namespace mozilla {
31
32 class ServoCSSRuleList;
33 class ServoStyleSet;
34
35 typedef MozPromise</* Dummy */ bool,
36 /* Dummy */ bool,
37 /* IsExclusive = */ true>
38 StyleSheetParsePromise;
39
40 namespace css {
41 class GroupRule;
42 class Loader;
43 class LoaderReusableStyleSheets;
44 class Rule;
45 class SheetLoadData;
46 } // namespace css
47
48 namespace dom {
49 class CSSImportRule;
50 class CSSRuleList;
51 class DocumentOrShadowRoot;
52 class MediaList;
53 class ShadowRoot;
54 class SRIMetadata;
55 struct CSSStyleSheetInit;
56 } // namespace dom
57
58 enum class StyleSheetState : uint8_t {
59 // Whether the sheet is disabled. Sheets can be made disabled via CSSOM, or
60 // via alternate links and such.
61 Disabled = 1 << 0,
62 // Whether the sheet is complete. The sheet is complete if it's finished
63 // loading. See StyleSheet::SetComplete.
64 Complete = 1 << 1,
65 // Whether we've forced a unique inner. StyleSheet objects share an 'inner'
66 // StyleSheetInfo object if they share URL, CORS mode, etc.
67 //
68 // See the Loader's `mCompleteSheets` and `mLoadingSheets`.
69 ForcedUniqueInner = 1 << 2,
70 // Whether this stylesheet has suffered any modification to the rules via
71 // CSSOM.
72 ModifiedRules = 1 << 3,
73 // Same flag, but devtools clears it in some specific situations.
74 //
75 // Used to control whether devtools shows the rule in its authored form or
76 // not.
77 ModifiedRulesForDevtools = 1 << 4,
78 // Whether modifications to the sheet are currently disallowed.
79 // This flag is set during the async Replace() function to ensure
80 // that the sheet is not modified until the promise is resolved.
81 ModificationDisallowed = 1 << 5,
82 };
83
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StyleSheetState)84 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StyleSheetState)
85
86 class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
87 StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentSheetToUse,
88 dom::CSSImportRule* aOwnerRuleToUse,
89 dom::DocumentOrShadowRoot* aDocOrShadowRootToUse,
90 dom::Document* aConstructorDocToUse, nsINode* aOwningNodeToUse);
91
92 virtual ~StyleSheet();
93
94 using State = StyleSheetState;
95
96 public:
97 StyleSheet(css::SheetParsingMode aParsingMode, CORSMode aCORSMode,
98 const dom::SRIMetadata& aIntegrity);
99
100 static already_AddRefed<StyleSheet> Constructor(const dom::GlobalObject&,
101 const dom::CSSStyleSheetInit&,
102 ErrorResult&);
103
104 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
105 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StyleSheet)
106
107 already_AddRefed<StyleSheet> CreateEmptyChildSheet(
108 already_AddRefed<dom::MediaList> aMediaList) const;
109
110 bool HasRules() const;
111
112 // Parses a stylesheet. The load data argument corresponds to the
113 // SheetLoadData for this stylesheet.
114 // NOTE: ParseSheet can run synchronously or asynchronously
115 // based on the result of `AllowParallelParse`
116 RefPtr<StyleSheetParsePromise> ParseSheet(css::Loader&,
117 const nsACString& aBytes,
118 css::SheetLoadData&);
119
120 // Common code that needs to be called after servo finishes parsing. This is
121 // shared between the parallel and sequential paths.
122 void FinishAsyncParse(
123 already_AddRefed<RawServoStyleSheetContents> aSheetContents);
124
125 // Similar to `ParseSheet`, but guarantees that
126 // parsing will be performed synchronously.
127 // NOTE: ParseSheet can still run synchronously.
128 // This is not a strict alternative.
129 //
130 // The load data may be null sometimes.
131 void ParseSheetSync(
132 css::Loader* aLoader, const nsACString& aBytes,
133 css::SheetLoadData* aLoadData, uint32_t aLineNumber,
134 css::LoaderReusableStyleSheets* aReusableSheets = nullptr);
135
136 void ReparseSheet(const nsACString& aInput, ErrorResult& aRv);
137
138 const RawServoStyleSheetContents* RawContents() const {
139 return Inner().mContents;
140 }
141
142 void SetContentsForImport(const RawServoStyleSheetContents* aContents) {
143 MOZ_ASSERT(!Inner().mContents);
144 Inner().mContents = aContents;
145 }
146
147 URLExtraData* URLData() const { return Inner().mURLData; }
148
149 // nsICSSLoaderObserver interface
150 NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
151 nsresult aStatus) final;
152
153 // Internal GetCssRules methods which do not have security check and
154 // completeness check.
155 ServoCSSRuleList* GetCssRulesInternal();
156
157 // Returns the stylesheet's Servo origin as a StyleOrigin value.
158 StyleOrigin GetOrigin() const;
159
160 /**
161 * The different changes that a stylesheet may go through.
162 *
163 * Used by the StyleSets in order to handle more efficiently some kinds of
164 * changes.
165 */
166 enum class ChangeType {
167 Added,
168 Removed,
169 ApplicableStateChanged,
170 RuleAdded,
171 RuleRemoved,
172 RuleChanged,
173 };
174
175 void SetOwningNode(nsINode* aOwningNode) { mOwningNode = aOwningNode; }
176
177 css::SheetParsingMode ParsingMode() const { return mParsingMode; }
178 dom::CSSStyleSheetParsingMode ParsingModeDOM();
179
180 /**
181 * Whether the sheet is complete.
182 */
183 bool IsComplete() const { return bool(mState & State::Complete); }
184
185 void SetComplete();
186
187 void SetEnabled(bool aEnabled) { SetDisabled(!aEnabled); }
188
189 // Whether the sheet is for an inline <style> element.
190 bool IsInline() const { return !GetOriginalURI(); }
191
192 nsIURI* GetSheetURI() const { return Inner().mSheetURI; }
193
194 /**
195 * Get the URI this sheet was originally loaded from, if any. Can return null.
196 */
197 nsIURI* GetOriginalURI() const { return Inner().mOriginalSheetURI; }
198
199 nsIURI* GetBaseURI() const { return Inner().mBaseURI; }
200
201 /**
202 * SetURIs must be called on all sheets before parsing into them.
203 * SetURIs may only be called while the sheet is 1) incomplete and 2)
204 * has no rules in it.
205 *
206 * FIXME(emilio): Can we pass this down when constructing the sheet instead?
207 */
208 inline void SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI,
209 nsIURI* aBaseURI);
210
211 /**
212 * Whether the sheet is applicable. A sheet that is not applicable
213 * should never be inserted into a style set. A sheet may not be
214 * applicable for a variety of reasons including being disabled and
215 * being incomplete.
216 */
217 bool IsApplicable() const { return !Disabled() && IsComplete(); }
218
219 already_AddRefed<StyleSheet> Clone(
220 StyleSheet* aCloneParent, dom::CSSImportRule* aCloneOwnerRule,
221 dom::DocumentOrShadowRoot* aCloneDocumentOrShadowRoot,
222 nsINode* aCloneOwningNode) const;
223
224 /**
225 * Creates a clone of the adopted style sheet as though it were constructed
226 * by aConstructorDocument. This should only be used for printing.
227 */
228 already_AddRefed<StyleSheet> CloneAdoptedSheet(
229 dom::Document& aConstructorDocument) const;
230
231 bool HasForcedUniqueInner() const {
232 return bool(mState & State::ForcedUniqueInner);
233 }
234
235 bool HasModifiedRules() const { return bool(mState & State::ModifiedRules); }
236
237 bool HasModifiedRulesForDevtools() const {
238 return bool(mState & State::ModifiedRulesForDevtools);
239 }
240
241 bool HasUniqueInner() const { return Inner().mSheets.Length() == 1; }
242
243 void AssertHasUniqueInner() const { MOZ_ASSERT(HasUniqueInner()); }
244
245 void EnsureUniqueInner();
246
247 // Returns the DocumentOrShadowRoot* that owns us, if any.
248 //
249 // TODO(emilio): Maybe rename to GetOwner*() or such? Might be
250 // confusing with nsINode::OwnerDoc and such.
251 dom::DocumentOrShadowRoot* GetAssociatedDocumentOrShadowRoot() const;
252
253 // Whether this stylesheet is kept alive by the associated or constructor
254 // document somehow, and thus at least has the same lifetime as
255 // GetAssociatedDocument().
256 dom::Document* GetKeptAliveByDocument() const;
257
258 // If this is a constructed style sheet, return mConstructorDocument.
259 // Otherwise return the document we're associated to,
260 // via mDocumentOrShadowRoot.
261 //
262 // Non-null iff GetAssociatedDocumentOrShadowRoot is non-null.
263 dom::Document* GetAssociatedDocument() const;
264
265 void SetAssociatedDocumentOrShadowRoot(dom::DocumentOrShadowRoot*);
266 void ClearAssociatedDocumentOrShadowRoot() {
267 SetAssociatedDocumentOrShadowRoot(nullptr);
268 }
269
270 nsINode* GetOwnerNode() const { return mOwningNode; }
271
272 StyleSheet* GetParentSheet() const { return mParentSheet; }
273
274 void SetOwnerRule(dom::CSSImportRule* aOwnerRule) {
275 mOwnerRule = aOwnerRule; /* Not ref counted */
276 }
277 dom::CSSImportRule* GetOwnerRule() const { return mOwnerRule; }
278
279 void AppendStyleSheet(StyleSheet&);
280
281 // Append a stylesheet to the child list without calling WillDirty.
282 void AppendStyleSheetSilently(StyleSheet&);
283
284 const nsTArray<RefPtr<StyleSheet>>& ChildSheets() const {
285 #ifdef DEBUG
286 for (StyleSheet* child : Inner().mChildren) {
287 MOZ_ASSERT(child->GetParentSheet());
288 MOZ_ASSERT(child->GetParentSheet()->mInner == mInner);
289 }
290 #endif
291 return Inner().mChildren;
292 }
293
294 // Principal() never returns a null pointer.
295 nsIPrincipal* Principal() const { return Inner().mPrincipal; }
296
297 /**
298 * SetPrincipal should be called on all sheets before parsing into them.
299 * This can only be called once with a non-null principal.
300 *
301 * Calling this with a null pointer is allowed and is treated as a no-op.
302 *
303 * FIXME(emilio): Can we get this at construction time instead?
304 */
305 void SetPrincipal(nsIPrincipal* aPrincipal) {
306 StyleSheetInfo& info = Inner();
307 MOZ_ASSERT(!info.mPrincipalSet, "Should only set principal once");
308 if (aPrincipal) {
309 info.mPrincipal = aPrincipal;
310 #ifdef DEBUG
311 info.mPrincipalSet = true;
312 #endif
313 }
314 }
315
316 void SetTitle(const nsAString& aTitle) { mTitle = aTitle; }
317 void SetMedia(already_AddRefed<dom::MediaList> aMedia);
318
319 // Get this style sheet's CORS mode
320 CORSMode GetCORSMode() const { return Inner().mCORSMode; }
321
322 // Get this style sheet's ReferrerInfo
323 nsIReferrerInfo* GetReferrerInfo() const { return Inner().mReferrerInfo; }
324
325 // Set this style sheet's ReferrerInfo
326 void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
327 Inner().mReferrerInfo = aReferrerInfo;
328 }
329
330 // Get this style sheet's integrity metadata
331 void GetIntegrity(dom::SRIMetadata& aResult) const {
332 aResult = Inner().mIntegrity;
333 }
334
335 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
336 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
337 void List(FILE* aOut = stdout, int32_t aIndex = 0);
338 #endif
339
340 // WebIDL StyleSheet API
341 void GetType(nsAString& aType);
342 void GetHref(nsAString& aHref, ErrorResult& aRv);
343 // GetOwnerNode is defined above.
344 StyleSheet* GetParentStyleSheet() const { return GetParentSheet(); }
345 void GetTitle(nsAString& aTitle);
346 dom::MediaList* Media();
347 bool Disabled() const { return bool(mState & State::Disabled); }
348 void SetDisabled(bool aDisabled);
349 void GetSourceMapURL(nsAString& aTitle);
350 void SetSourceMapURL(const nsAString& aSourceMapURL);
351 void SetSourceMapURLFromComment(const nsAString& aSourceMapURLFromComment);
352 void GetSourceURL(nsAString& aSourceURL);
353 void SetSourceURL(const nsAString& aSourceURL);
354
355 // WebIDL CSSStyleSheet API
356 // Can't be inline because we can't include ImportRule here. And can't be
357 // called GetOwnerRule because that would be ambiguous with the ImportRule
358 // version.
359 css::Rule* GetDOMOwnerRule() const;
360 dom::CSSRuleList* GetCssRules(nsIPrincipal& aSubjectPrincipal, ErrorResult&);
361 uint32_t InsertRule(const nsAString& aRule, uint32_t aIndex,
362 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
363 void DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal,
364 ErrorResult& aRv);
365 int32_t AddRule(const nsAString& aSelector, const nsAString& aBlock,
366 const dom::Optional<uint32_t>& aIndex,
367 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
368 already_AddRefed<dom::Promise> Replace(const nsACString& aText, ErrorResult&);
369 void ReplaceSync(const nsACString& aText, ErrorResult&);
370 bool ModificationDisallowed() const {
371 return bool(mState & State::ModificationDisallowed);
372 }
373
374 // Called before and after the asynchronous Replace() function
375 // to disable/re-enable modification while there is a pending promise.
376 void SetModificationDisallowed(bool aDisallowed) {
377 MOZ_ASSERT(IsConstructed());
378 MOZ_ASSERT(!IsReadOnly());
379 if (aDisallowed) {
380 mState |= State::ModificationDisallowed;
381 // Sheet will be re-set to complete when its rules are replaced
382 mState &= ~State::Complete;
383 if (!Disabled()) {
384 ApplicableStateChanged(false);
385 }
386 } else {
387 mState &= ~State::ModificationDisallowed;
388 }
389 }
390
391 // True if the sheet was created through the Constructable StyleSheets API
392 bool IsConstructed() const { return !!mConstructorDocument; }
393
394 // True if any of this sheet's ancestors were created through the
395 // Constructable StyleSheets API
396 bool SelfOrAncestorIsConstructed() const {
397 return OutermostSheet().IsConstructed();
398 }
399
400 // Ture if the sheet's constructor document matches the given document
401 bool ConstructorDocumentMatches(const dom::Document& aDocument) const {
402 return mConstructorDocument == &aDocument;
403 }
404
405 // Add a document or shadow root to the list of adopters.
406 // Adopters will be notified when styles are changed.
407 void AddAdopter(dom::DocumentOrShadowRoot& aAdopter) {
408 MOZ_ASSERT(IsConstructed());
409 MOZ_ASSERT(!mAdopters.Contains(&aAdopter));
410 mAdopters.AppendElement(&aAdopter);
411 }
412
413 // Remove a document or shadow root from the list of adopters.
414 void RemoveAdopter(dom::DocumentOrShadowRoot& aAdopter) {
415 // Cannot assert IsConstructed() because this can run after unlink.
416 mAdopters.RemoveElement(&aAdopter);
417 }
418
419 // WebIDL miscellaneous bits
420 inline dom::ParentObject GetParentObject() const;
421 JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
422
423 // Changes to sheets should be after a WillDirty call.
424 void WillDirty();
425
426 // Called when a rule changes from CSSOM.
427 //
428 // FIXME(emilio): This shouldn't allow null, but MediaList doesn't know about
429 // it's owning media rule, plus it's used for the stylesheet media itself.
430 void RuleChanged(css::Rule*);
431
432 void AddStyleSet(ServoStyleSet* aStyleSet);
433 void DropStyleSet(ServoStyleSet* aStyleSet);
434
435 nsresult DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex);
436 nsresult InsertRuleIntoGroup(const nsAString& aRule, css::GroupRule* aGroup,
437 uint32_t aIndex);
438
439 // Find the ID of the owner inner window.
440 uint64_t FindOwningWindowInnerID() const;
441
442 // Copy the contents of this style sheet into the shared memory buffer managed
443 // by aBuilder. Returns the pointer into the buffer that the sheet contents
444 // were stored at. (The returned pointer is to an Arc<Locked<Rules>> value,
445 // or null, with a filled in aErrorMessage, on failure.)
446 const ServoCssRules* ToShared(RawServoSharedMemoryBuilder* aBuilder,
447 nsCString& aErrorMessage);
448
449 // Sets the contents of this style sheet to the specified aSharedRules
450 // pointer, which must be a pointer somewhere in the aSharedMemory buffer
451 // as previously returned by a ToShared() call.
452 void SetSharedContents(const ServoCssRules* aSharedRules);
453
454 // Whether this style sheet should not allow any modifications.
455 //
456 // This is true for any User Agent sheets once they are complete.
457 bool IsReadOnly() const;
458
459 // Removes a stylesheet from its parent sheet child list, if any.
460 void RemoveFromParent();
461
462 // Resolves mReplacePromise with this sheet.
463 void MaybeResolveReplacePromise();
464
465 // Rejects mReplacePromise with a NetworkError.
466 void MaybeRejectReplacePromise();
467
468 private:
469 void SetModifiedRules() {
470 mState |= State::ModifiedRules | State::ModifiedRulesForDevtools;
471 }
472
473 const StyleSheet& OutermostSheet() const {
474 auto* current = this;
475 while (current->mParentSheet) {
476 MOZ_ASSERT(!current->mDocumentOrShadowRoot,
477 "Shouldn't be set on child sheets");
478 MOZ_ASSERT(!current->mConstructorDocument,
479 "Shouldn't be set on child sheets");
480 current = current->mParentSheet;
481 }
482 return *current;
483 }
484
485 StyleSheetInfo& Inner() {
486 MOZ_ASSERT(mInner);
487 return *mInner;
488 }
489
490 const StyleSheetInfo& Inner() const {
491 MOZ_ASSERT(mInner);
492 return *mInner;
493 }
494
495 // Check if the rules are available for read and write.
496 // It does the security check as well as whether the rules have been
497 // completely loaded. aRv will have an exception set if this function
498 // returns false.
499 bool AreRulesAvailable(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
500
501 void SetURLExtraData();
502
503 protected:
504 // Internal methods which do not have security check and completeness check.
505 uint32_t InsertRuleInternal(const nsAString& aRule, uint32_t aIndex,
506 ErrorResult&);
507 void DeleteRuleInternal(uint32_t aIndex, ErrorResult&);
508 nsresult InsertRuleIntoGroupInternal(const nsAString& aRule,
509 css::GroupRule* aGroup, uint32_t aIndex);
510
511 // Common tail routine for the synchronous and asynchronous parsing paths.
512 void FinishParse();
513
514 // Take the recently cloned sheets from the `@import` rules, and reparent them
515 // correctly to `aPrimarySheet`.
516 void BuildChildListAfterInnerClone();
517
518 void DropRuleList();
519
520 // Called when a rule is removed from the sheet from CSSOM.
521 void RuleAdded(css::Rule&);
522
523 // Called when a rule is added to the sheet from CSSOM.
524 void RuleRemoved(css::Rule&);
525
526 // Called when a stylesheet is cloned.
527 void StyleSheetCloned(StyleSheet&);
528
529 // Notifies that the applicable state changed.
530 // aApplicable is the value that we expect to get from IsApplicable().
531 // assertion will fail if the expectation does not match reality.
532 void ApplicableStateChanged(bool aApplicable);
533
534 void UnparentChildren();
535
536 void LastRelease();
537
538 // Return success if the subject principal subsumes the principal of our
539 // inner, error otherwise. This will also succeed if access is allowed by
540 // CORS. In that case, it will set the principal of the inner to the
541 // subject principal.
542 void SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal,
543 ErrorResult& aRv);
544
545 // Drop our reference to mMedia
546 void DropMedia();
547
548 // Unlink our inner, if needed, for cycle collection.
549 void UnlinkInner();
550 // Traverse our inner, if needed, for cycle collection
551 void TraverseInner(nsCycleCollectionTraversalCallback&);
552
553 // Return whether the given @import rule has pending child sheet.
554 static bool RuleHasPendingChildSheet(css::Rule* aRule);
555
556 StyleSheet* mParentSheet; // weak ref
557
558 // A pointer to the sheets relevant global object.
559 // This is populated when the sheet gets an associated document.
560 // This is required for the sheet to be able to create a promise.
561 // https://html.spec.whatwg.org/#concept-relevant-everything
562 nsCOMPtr<nsIGlobalObject> mRelevantGlobal;
563
564 RefPtr<dom::Document> mConstructorDocument;
565
566 // Will be set in the Replace() function and resolved/rejected by the
567 // sheet once its rules have been replaced and the sheet is complete again.
568 RefPtr<dom::Promise> mReplacePromise;
569
570 nsString mTitle;
571
572 // weak ref; parents maintain this for their children
573 dom::DocumentOrShadowRoot* mDocumentOrShadowRoot;
574 nsINode* mOwningNode; // weak ref
575 dom::CSSImportRule* mOwnerRule; // weak ref
576
577 RefPtr<dom::MediaList> mMedia;
578
579 // mParsingMode controls access to nonstandard style constructs that
580 // are not safe for use on the public Web but necessary in UA sheets
581 // and/or useful in user sheets.
582 //
583 // FIXME(emilio): Given we store the parsed contents in the Inner, this should
584 // probably also move there.
585 css::SheetParsingMode mParsingMode;
586
587 State mState;
588
589 // Core information we get from parsed sheets, which are shared amongst
590 // StyleSheet clones.
591 //
592 // Always nonnull until LastRelease().
593 StyleSheetInfo* mInner;
594
595 nsTArray<ServoStyleSet*> mStyleSets;
596
597 RefPtr<ServoCSSRuleList> mRuleList;
598
599 MozPromiseHolder<StyleSheetParsePromise> mParsePromise;
600
601 nsTArray<dom::DocumentOrShadowRoot*> mAdopters;
602
603 // Make StyleSheetInfo and subclasses into friends so they can use
604 // ChildSheetListBuilder.
605 friend struct StyleSheetInfo;
606 };
607
608 } // namespace mozilla
609
610 #endif // mozilla_StyleSheet_h
611