From 3a02ab4d4d2b7890ac1eb0b35494cd69eb8a32d4 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 2 Feb 2021 20:52:33 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1131308 (part 0) - Fix minor problems with RemovingIterator. r=froydnj. (5c232c9ca) - Bug 1176163 - Remove remaining uses of PL_DHashTableEnumerate() from xpcom/. r=froydnj. (bd2478b3a) - Bug 1179657 - Remove PL_DHASHMETER. r=froydnj. (30f581478) - Bug 1131308 (part 1) - Improve PLDHashTable's internal checking. r=froydnj. (faba4a3b9) - Bug 1180084 - Convert TestPLDHash.cpp to a gtest. r=froydnj. (de6ee3c5c) - Bug 1171578 - Avoid crashing if indexedDB is used at a particular point in the worker shutdown sequence, r=baku. (f325c218e) - Bug 804975 - Part 1: Expose WritingMode bits. r=dbaron (0635d01dd) - Bug 1072101 - Part 1: Implement FontFaceSet.size. r=peterv (7e0b4848a) - Bug 1144977 - Part 1: Don't include FontFace objects in more than one loadingdone/loadingerror event. r=jdaggett (4d10b3269) - Bug 1072101 - Part 2: Implement FontFaceSet.{entries,values}. r=peterv (1c636f4f1) - Bug 1072101 - Part 3: Implement FontFaceSet.forEach. r=peterv (842a5cdbe) - Bug 1072101 - Part 4: Implement FontFaceSet.{keys,@@iterator}. r=peterv (4dfb3afe9) - Bug 1072101 - Part 5: Remove indexed property access on FontFaceSet. r=peterv (edef47d70) - Bug 1072101 - Unified build fix; no review. (7c1538f6c) - Bug 861449 - Incremental css::Rule destroyer. r=dbaron (5e60f4394) - Bug 1175800 - Remove typedefs from nsComputedDOMStyle.cpp as they interfere with others under unified compilation. r=bzbarsky (2bd45e7ed) - Bug 1147766 - Part 1: Add a mozilla::RangedArray class, for fixed length arrays with a non-zero base index. r=waldo (42be428bc) - Bug 1147766 - Part 2: Replace FixedStyleStructArray with mozilla::RangedArray. r=dbaron (4d28a0a5e) - Bug 1171282 - Avoid some unnecessary |operator new| null-checks in layout/. r=dholbert. (60e1690c1) - Bug 1168664 - Stop mistakenly using the transform reference box's width to calculate the Z component in ProcessMatrix3D. r=mattwoodrow (18f9021b1) - Bug 1171842 - Use jump table instead of nested if statements for peeking compute function of style struct. r=dbaron (a5160b962) - Bug 1170173 - Parse CSS 'contain' property. r=dholbert (3d8a37dee) - Bug 1144607 part 1 - Remove CounterStyleManager::BuildCounterStyle for anonymous counter styles. r=dbaron (331397d01) - Bug 1144607 part 2 - Support string value for list-style-tyle. r=dbaron (0b4bca0c7) - Bug 1144607 part 3 - Reftests for string value on list-style-type. r=dbaron (2a483f621) - Bug 1144607 followup - Fix submitted reftest for string value of list-style-type. DONTBUILD (ad23ac443) - Bug 1151220 - [css-grid] Fix a couple of typos in InitializeTrackSize. r=dholbert (840f3b8a4) - Bug 1151201 - [css-grid] Update parsing of 'auto' in track-sizing functions to latest version of the spec. r=simon.sapin (abdb0c0ea) --- dom/bindings/Bindings.conf | 4 + dom/indexedDB/IDBFactory.cpp | 5 + dom/indexedDB/IDBRequest.cpp | 26 +- dom/webidl/FontFaceSet.webidl | 37 +- dom/workers/WorkerPrivate.cpp | 10 +- layout/base/nsCounterManager.cpp | 2 +- layout/generic/WritingModes.h | 2 + layout/generic/nsFrameList.h | 2 +- layout/generic/nsGridContainerFrame.cpp | 6 +- layout/generic/nsLineBox.cpp | 2 - layout/generic/nsLineBox.h | 4 +- .../list-style-type-string-001-ref.html | 29 ++ .../lists-3/list-style-type-string-001a.html | 27 ++ .../lists-3/list-style-type-string-001b.html | 27 ++ .../w3c-css/submitted/lists-3/reftest.list | 3 + .../reftests/w3c-css/submitted/reftest.list | 3 + layout/style/CSSStyleSheet.h | 4 +- layout/style/CounterStyleManager.cpp | 24 +- layout/style/CounterStyleManager.h | 4 +- layout/style/FontFaceSet.cpp | 94 ++++- layout/style/FontFaceSet.h | 21 +- layout/style/FontFaceSetIterator.cpp | 75 ++++ layout/style/FontFaceSetIterator.h | 39 ++ layout/style/GroupRule.h | 8 +- layout/style/IncrementalClearCOMRuleArray.cpp | 80 ++++ layout/style/IncrementalClearCOMRuleArray.h | 28 ++ layout/style/generate-stylestructlist.py | 72 +--- layout/style/moz.build | 4 + layout/style/nsCSSKeywordList.h | 3 + layout/style/nsCSSParser.cpp | 43 ++- layout/style/nsCSSPropList.h | 12 + layout/style/nsCSSProps.cpp | 9 + layout/style/nsCSSProps.h | 1 + layout/style/nsCSSValue.cpp | 14 + layout/style/nsComputedDOMStyle.cpp | 57 ++- layout/style/nsComputedDOMStyle.h | 1 + layout/style/nsComputedDOMStylePropertyList.h | 1 + layout/style/nsRuleNode.cpp | 64 ++- layout/style/nsRuleNode.h | 33 +- layout/style/nsStyleConsts.h | 13 + layout/style/nsStyleSet.cpp | 5 +- layout/style/nsStyleStruct.cpp | 19 +- layout/style/nsStyleStruct.h | 1 + layout/style/nsStyleTransformMatrix.cpp | 3 +- layout/style/test/property_database.js | 48 ++- .../test/test_grid_container_shorthands.html | 14 +- mfbt/RangedArray.h | 44 +++ mfbt/moz.build | 1 + modules/libpref/init/all.js | 3 + testing/profiles/prefs_general.js | 3 + ...ser_ruleview_completion-new-property_02.js | 2 +- xpcom/glue/nsBaseHashtable.h | 90 ++--- xpcom/glue/nsTHashtable.h | 45 +-- xpcom/glue/pldhash.cpp | 365 +++++------------- xpcom/glue/pldhash.h | 194 +++++++--- xpcom/tests/{ => gtest}/TestPLDHash.cpp | 171 ++------ xpcom/tests/gtest/moz.build | 1 + xpcom/tests/moz.build | 1 - 58 files changed, 1126 insertions(+), 777 deletions(-) create mode 100644 layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001-ref.html create mode 100644 layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001a.html create mode 100644 layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001b.html create mode 100644 layout/reftests/w3c-css/submitted/lists-3/reftest.list create mode 100644 layout/style/FontFaceSetIterator.cpp create mode 100644 layout/style/FontFaceSetIterator.h create mode 100644 layout/style/IncrementalClearCOMRuleArray.cpp create mode 100644 layout/style/IncrementalClearCOMRuleArray.h create mode 100644 mfbt/RangedArray.h rename xpcom/tests/{ => gtest}/TestPLDHash.cpp (70%) diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 273be83ced..da0855276c 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -488,6 +488,10 @@ DOMInterfaces = { 'wrapperCache': False, }, +'FontFaceSetIterator': { + 'wrapperCache': False, +}, + 'FormData': { 'nativeType': 'nsFormData' }, diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index a22d933116..715b781363 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -765,6 +765,11 @@ IDBFactory::OpenInternal(nsIPrincipal* aPrincipal, JS::Rooted scriptOwner(autoJS.cx(), mOwningObject); request = IDBOpenDBRequest::CreateForJS(this, scriptOwner); + if (!request) { + MOZ_ASSERT(!NS_IsMainThread()); + aRv.ThrowUncatchableException(); + return nullptr; + } } MOZ_ASSERT(request); diff --git a/dom/indexedDB/IDBRequest.cpp b/dom/indexedDB/IDBRequest.cpp index 4355a22bb4..193e90cd24 100644 --- a/dom/indexedDB/IDBRequest.cpp +++ b/dom/indexedDB/IDBRequest.cpp @@ -442,11 +442,19 @@ class IDBOpenDBRequest::WorkerFeature final : public mozilla::dom::workers::WorkerFeature { WorkerPrivate* mWorkerPrivate; +#ifdef DEBUG + // This is only here so that assertions work in the destructor even if + // NoteAddFeatureFailed was called. + WorkerPrivate* mWorkerPrivateDEBUG; +#endif public: explicit WorkerFeature(WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) +#ifdef DEBUG + , mWorkerPrivateDEBUG(aWorkerPrivate) +#endif { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); @@ -456,11 +464,24 @@ public: ~WorkerFeature() { - mWorkerPrivate->AssertIsOnWorkerThread(); +#ifdef DEBUG + mWorkerPrivateDEBUG->AssertIsOnWorkerThread(); +#endif MOZ_COUNT_DTOR(IDBOpenDBRequest::WorkerFeature); - mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this); + if (mWorkerPrivate) { + mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this); + } + } + + void + NoteAddFeatureFailed() + { + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + + mWorkerPrivate = nullptr; } private: @@ -527,6 +548,7 @@ IDBOpenDBRequest::CreateForJS(IDBFactory* aFactory, nsAutoPtr feature(new WorkerFeature(workerPrivate)); if (NS_WARN_IF(!workerPrivate->AddFeature(cx, feature))) { + feature->NoteAddFeatureFailed(); return nullptr; } diff --git a/dom/webidl/FontFaceSet.webidl b/dom/webidl/FontFaceSet.webidl index 68cf4b0737..d1671efd6f 100644 --- a/dom/webidl/FontFaceSet.webidl +++ b/dom/webidl/FontFaceSet.webidl @@ -10,6 +10,21 @@ * liability, trademark and document use rules apply. */ +// To implement FontFaceSet's iterator until we can use setlike. +dictionary FontFaceSetIteratorResult +{ + required any value; + required boolean done; +}; + +// To implement FontFaceSet's iterator until we can use setlike. +[NoInterfaceObject] +interface FontFaceSetIterator { + [Throws] FontFaceSetIteratorResult next(); +}; + +callback FontFaceSetForEachCallback = void (FontFace value, FontFace key, FontFaceSet set); + enum FontFaceSetLoadStatus { "loading", "loaded" }; // Bug 1072762 is for the FontFaceSet constructor. @@ -17,19 +32,16 @@ enum FontFaceSetLoadStatus { "loading", "loaded" }; [Pref="layout.css.font-loading-api.enabled"] interface FontFaceSet : EventTarget { - // Emulate the Set interface, until we can extend Set correctly. - // Implementing these commented out operations and the iterator is - // bug 1072101. - // readonly attribute unsigned long size; + // Emulate setlike behavior until we can use that directly. + readonly attribute unsigned long size; [Throws] void add(FontFace font); boolean has(FontFace font); [Throws] boolean delete(FontFace font); void clear(); - // Iterator entries(); + [NewObject] FontFaceSetIterator entries(); // Iterator keys(); - // Iterator values(); - // void forEach(ForEachCallback cb, optional any thisArg); - // FontFace iterator; + [NewObject, Alias=keys, Alias="@@iterator"] FontFaceSetIterator values(); + [Throws] void forEach(FontFaceSetForEachCallback cb, optional any thisArg); // -- events for when loading state changes attribute EventHandler onloading; @@ -52,12 +64,3 @@ interface FontFaceSet : EventTarget { // loading state, "loading" while one or more fonts loading, "loaded" otherwise readonly attribute FontFaceSetLoadStatus status; }; - -// This provides access to the FontFace objects in the FontFaceSet until we -// get iterators working (bug 1072101). Don't enable the pref for the CSS Font -// Loading API until the iterator is available, as we don't want to expose more -// indexed properties on the Web. -partial interface FontFaceSet { - getter FontFace (unsigned long index); - readonly attribute unsigned long length; -}; diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index c886364f30..7d4fbe51d1 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -5829,11 +5829,13 @@ WorkerPrivate::AddFeature(JSContext* aCx, WorkerFeature* aFeature) } MOZ_ASSERT(!mFeatures.Contains(aFeature), "Already know about this one!"); - mFeatures.AppendElement(aFeature); - return mFeatures.Length() == 1 ? - ModifyBusyCountFromWorker(aCx, true) : - true; + if (mFeatures.IsEmpty() && !ModifyBusyCountFromWorker(aCx, true)) { + return false; + } + + mFeatures.AppendElement(aFeature); + return true; } void diff --git a/layout/base/nsCounterManager.cpp b/layout/base/nsCounterManager.cpp index 56e0a8efb2..4254a9a2ec 100644 --- a/layout/base/nsCounterManager.cpp +++ b/layout/base/nsCounterManager.cpp @@ -55,7 +55,7 @@ nsCounterUseNode::GetCounterStyle() style.GetStringValue(ident); mCounterStyle = manager->BuildCounterStyle(ident); } else if (style.GetUnit() == eCSSUnit_Symbols) { - mCounterStyle = manager->BuildCounterStyle(style.GetArrayValue()); + mCounterStyle = new AnonymousCounterStyle(style.GetArrayValue()); } else { NS_NOTREACHED("Unknown counter style"); mCounterStyle = CounterStyleManager::GetDecimalStyle(); diff --git a/layout/generic/WritingModes.h b/layout/generic/WritingModes.h index b00200b528..8e09bece9b 100644 --- a/layout/generic/WritingModes.h +++ b/layout/generic/WritingModes.h @@ -468,6 +468,8 @@ public: return IsVertical() != aOther.IsVertical(); } + uint8_t GetBits() const { return mWritingMode; } + private: friend class LogicalPoint; friend class LogicalSize; diff --git a/layout/generic/nsFrameList.h b/layout/generic/nsFrameList.h index 362722c771..596d651e60 100644 --- a/layout/generic/nsFrameList.h +++ b/layout/generic/nsFrameList.h @@ -73,7 +73,7 @@ public: } /** - * Allocate a nsFrameList from the shell arena. + * Infallibly allocate a nsFrameList from the shell arena. */ void* operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW; diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index d6e16a7e0d..120c0d65d3 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -950,7 +950,8 @@ InitializeTrackSize(nscoord aPercentageBasis, { // http://dev.w3.org/csswg/css-grid/#algo-init nscoord& base = aTrackSize->mBase; - switch (aMaxCoord.GetUnit()) { + switch (aMinCoord.GetUnit()) { + case eStyleUnit_Auto: case eStyleUnit_Enumerated: case eStyleUnit_FlexFraction: base = 0; @@ -960,6 +961,7 @@ InitializeTrackSize(nscoord aPercentageBasis, } nscoord& limit = aTrackSize->mLimit; switch (aMaxCoord.GetUnit()) { + case eStyleUnit_Auto: case eStyleUnit_Enumerated: limit = NS_UNCONSTRAINEDSIZE; break; @@ -988,7 +990,7 @@ InitializeTrackSizes(nscoord aPercentageBasis, size_t i = 0; for (; i < len; ++i) { InitializeTrackSize(aPercentageBasis, - aMinSizingFunctions[i], aMinSizingFunctions[i], + aMinSizingFunctions[i], aMaxSizingFunctions[i], &aResults[i]); } for (; i < aResults.Length(); ++i) { diff --git a/layout/generic/nsLineBox.cpp b/layout/generic/nsLineBox.cpp index 20fdd63012..47746563f1 100644 --- a/layout/generic/nsLineBox.cpp +++ b/layout/generic/nsLineBox.cpp @@ -145,8 +145,6 @@ nsLineBox::NoteFramesMovedFrom(nsLineBox* aFromLine) } } -// Overloaded new operator. Uses an arena (which comes from the presShell) -// to perform the allocation. void* nsLineBox::operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW { diff --git a/layout/generic/nsLineBox.h b/layout/generic/nsLineBox.h index fdd54d3395..832fc1f685 100644 --- a/layout/generic/nsLineBox.h +++ b/layout/generic/nsLineBox.h @@ -206,8 +206,8 @@ private: nsLineBox(nsIFrame* aFrame, int32_t aCount, bool aIsBlock); ~nsLineBox(); - // Overloaded new operator. Uses an arena (which comes from the presShell) - // to perform the allocation. + // Infallible overloaded new operator. Uses an arena (which comes from the + // presShell) to perform the allocation. void* operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW; void operator delete(void* aPtr, size_t sz) = delete; diff --git a/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001-ref.html b/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001-ref.html new file mode 100644 index 0000000000..0d1b136287 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001-ref.html @@ -0,0 +1,29 @@ + + + + + CSS Reference: String value of list-style-type + + + + +
    +
  1. item -1
  2. +
  3. item 0
  4. +
  5. item 1
  6. +
  7. item 2
  8. +
+
    +
  • item -1
  • +
  • item 0
  • +
  • item 1
  • +
  • item 2
  • +
+ + diff --git a/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001a.html b/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001a.html new file mode 100644 index 0000000000..10f27f4fc0 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001a.html @@ -0,0 +1,27 @@ + + + + + CSS Test: String value of list-style-type + + + + + + +
    +
  1. item -1
  2. +
  3. item 0
  4. +
  5. item 1
  6. +
  7. item 2
  8. +
+
    +
  • item -1
  • +
  • item 0
  • +
  • item 1
  • +
  • item 2
  • +
+ + diff --git a/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001b.html b/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001b.html new file mode 100644 index 0000000000..d30f58984d --- /dev/null +++ b/layout/reftests/w3c-css/submitted/lists-3/list-style-type-string-001b.html @@ -0,0 +1,27 @@ + + + + + CSS Test: String value of list-style + + + + + + +
    +
  1. item -1
  2. +
  3. item 0
  4. +
  5. item 1
  6. +
  7. item 2
  8. +
+
    +
  • item -1
  • +
  • item 0
  • +
  • item 1
  • +
  • item 2
  • +
+ + diff --git a/layout/reftests/w3c-css/submitted/lists-3/reftest.list b/layout/reftests/w3c-css/submitted/lists-3/reftest.list new file mode 100644 index 0000000000..f914145ab8 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/lists-3/reftest.list @@ -0,0 +1,3 @@ +# Tests for list-style-type +== list-style-type-string-001a.html list-style-type-string-001-ref.html +== list-style-type-string-001b.html list-style-type-string-001-ref.html diff --git a/layout/reftests/w3c-css/submitted/reftest.list b/layout/reftests/w3c-css/submitted/reftest.list index 66cf951115..290b9eef3e 100644 --- a/layout/reftests/w3c-css/submitted/reftest.list +++ b/layout/reftests/w3c-css/submitted/reftest.list @@ -28,6 +28,9 @@ include fonts3/reftest.list # Image Values and Replaced Content Level 3 include images3/reftest.list +# Lists and Counters Level 3 +include lists-3/reftest.list + # Media Queries Level 3 # include mediaqueries3/reftest.list diff --git a/layout/style/CSSStyleSheet.h b/layout/style/CSSStyleSheet.h index d798952252..017fecd477 100644 --- a/layout/style/CSSStyleSheet.h +++ b/layout/style/CSSStyleSheet.h @@ -10,6 +10,7 @@ #define mozilla_CSSStyleSheet_h #include "mozilla/Attributes.h" +#include "mozilla/IncrementalClearCOMRuleArray.h" #include "mozilla/MemoryReporting.h" #include "mozilla/dom/Element.h" @@ -19,7 +20,6 @@ #include "nsIStyleSheet.h" #include "nsIDOMCSSStyleSheet.h" #include "nsICSSLoaderObserver.h" -#include "nsCOMArray.h" #include "nsTArrayForwardDeclare.h" #include "nsString.h" #include "mozilla/CORSMode.h" @@ -84,7 +84,7 @@ private: nsCOMPtr mOriginalSheetURI; // for GetHref. Can be null. nsCOMPtr mBaseURI; // for resolving relative URIs nsCOMPtr mPrincipal; - nsCOMArray mOrderedRules; + IncrementalClearCOMRuleArray mOrderedRules; nsAutoPtr mNameSpaceMap; // Linked list of child sheets. This is al fundamentally broken, because // each of the child sheets has a unique parent... We can only hope (and diff --git a/layout/style/CounterStyleManager.cpp b/layout/style/CounterStyleManager.cpp index 287ef979ce..62c43f60f9 100644 --- a/layout/style/CounterStyleManager.cpp +++ b/layout/style/CounterStyleManager.cpp @@ -1696,10 +1696,20 @@ CustomCounterStyle::GetExtendsRoot() return mExtendsRoot; } +AnonymousCounterStyle::AnonymousCounterStyle(const nsSubstring& aContent) + : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM) + , mSingleString(true) + , mSystem(NS_STYLE_COUNTER_SYSTEM_CYCLIC) +{ + mSymbols.SetCapacity(1); + mSymbols.AppendElement(aContent); +} + AnonymousCounterStyle::AnonymousCounterStyle(const nsCSSValue::Array* aParams) : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM) + , mSingleString(false) + , mSystem(aParams->Item(0).GetIntValue()) { - mSystem = aParams->Item(0).GetIntValue(); for (const nsCSSValueList* item = aParams->Item(1).GetListValue(); item; item = item->mNext) { item->mValue.GetStringValue(*mSymbols.AppendElement()); @@ -1716,7 +1726,11 @@ AnonymousCounterStyle::GetPrefix(nsAString& aResult) /* virtual */ void AnonymousCounterStyle::GetSuffix(nsAString& aResult) { - aResult = ' '; + if (IsSingleString()) { + aResult.Truncate(); + } else { + aResult = ' '; + } } /* virtual */ bool @@ -2028,12 +2042,6 @@ CounterStyleManager::BuildCounterStyle(const nsSubstring& aName) return data; } -CounterStyle* -CounterStyleManager::BuildCounterStyle(const nsCSSValue::Array* aParams) -{ - return new AnonymousCounterStyle(aParams); -} - /* static */ CounterStyle* CounterStyleManager::GetBuiltinStyle(int32_t aStyle) { diff --git a/layout/style/CounterStyleManager.h b/layout/style/CounterStyleManager.h index 124a86cf92..b44553066c 100644 --- a/layout/style/CounterStyleManager.h +++ b/layout/style/CounterStyleManager.h @@ -105,6 +105,7 @@ protected: class AnonymousCounterStyle final : public CounterStyle { public: + explicit AnonymousCounterStyle(const nsSubstring& aContent); explicit AnonymousCounterStyle(const nsCSSValue::Array* aValue); virtual void GetPrefix(nsAString& aResult) override; @@ -126,6 +127,7 @@ public: virtual AnonymousCounterStyle* AsAnonymous() override { return this; } + bool IsSingleString() const { return mSingleString; } uint8_t GetSystem() const { return mSystem; } const nsTArray& GetSymbols() const { return mSymbols; } @@ -134,6 +136,7 @@ public: private: ~AnonymousCounterStyle() {} + bool mSingleString; uint8_t mSystem; nsTArray mSymbols; }; @@ -156,7 +159,6 @@ public: } CounterStyle* BuildCounterStyle(const nsSubstring& aName); - CounterStyle* BuildCounterStyle(const nsCSSValue::Array* aParams); static CounterStyle* GetBuiltinStyle(int32_t aStyle); static CounterStyle* GetNoneStyle() diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp index d8868e8312..92c777e935 100644 --- a/layout/style/FontFaceSet.cpp +++ b/layout/style/FontFaceSet.cpp @@ -12,6 +12,7 @@ #include "mozilla/dom/CSSFontFaceLoadEvent.h" #include "mozilla/dom/CSSFontFaceLoadEventBinding.h" #include "mozilla/dom/FontFaceSetBinding.h" +#include "mozilla/dom/FontFaceSetIterator.h" #include "mozilla/dom/Promise.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/Preferences.h" @@ -50,7 +51,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHel for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRuleFaces[i].mFontFace); } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonRuleFaces); + for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonRuleFaces[i].mFontFace); + } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelper) @@ -60,7 +63,9 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelpe for (size_t i = 0; i < tmp->mRuleFaces.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_UNLINK(mRuleFaces[i].mFontFace); } - NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonRuleFaces); + for (size_t i = 0; i < tmp->mNonRuleFaces.Length(); i++) { + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonRuleFaces[i].mFontFace); + } NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_ADDREF_INHERITED(FontFaceSet, DOMEventTargetHelper) @@ -234,10 +239,19 @@ FontFaceSet::Add(FontFace& aFontFace, ErrorResult& aRv) aFontFace.SetIsInFontFaceSet(true); - MOZ_ASSERT(!mNonRuleFaces.Contains(&aFontFace), - "FontFace should not occur in mNonRuleFaces twice"); +#ifdef DEBUG + for (const FontFaceRecord& rec : mNonRuleFaces) { + MOZ_ASSERT(rec.mFontFace != &aFontFace, + "FontFace should not occur in mNonRuleFaces twice"); + } +#endif - mNonRuleFaces.AppendElement(&aFontFace); + FontFaceRecord* rec = mNonRuleFaces.AppendElement(); + rec->mFontFace = &aFontFace; + rec->mSheetType = 0; // unused for mNonRuleFaces + rec->mLoadEventShouldFire = + aFontFace.Status() == FontFaceLoadStatus::Unloaded || + aFontFace.Status() == FontFaceLoadStatus::Loading; mNonRuleFacesDirty = true; mPresContext->RebuildUserFontSet(); @@ -256,7 +270,7 @@ FontFaceSet::Clear() } for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { - FontFace* f = mNonRuleFaces[i]; + FontFace* f = mNonRuleFaces[i].mFontFace; f->SetIsInFontFaceSet(false); MOZ_ASSERT(!mUnavailableFaces.Contains(f), @@ -282,7 +296,15 @@ FontFaceSet::Delete(FontFace& aFontFace, ErrorResult& aRv) return false; } - if (!mNonRuleFaces.RemoveElement(&aFontFace)) { + bool removed = false; + for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { + if (mNonRuleFaces[i].mFontFace == &aFontFace) { + mNonRuleFaces.RemoveElementAt(i); + removed = true; + break; + } + } + if (!removed) { return false; } @@ -316,27 +338,24 @@ FontFaceSet::Has(FontFace& aFontFace) } FontFace* -FontFaceSet::IndexedGetter(uint32_t aIndex, bool& aFound) +FontFaceSet::GetFontFaceAt(uint32_t aIndex) { mPresContext->FlushUserFontSet(); if (aIndex < mRuleFaces.Length()) { - aFound = true; return mRuleFaces[aIndex].mFontFace; } aIndex -= mRuleFaces.Length(); if (aIndex < mNonRuleFaces.Length()) { - aFound = true; - return mNonRuleFaces[aIndex]; + return mNonRuleFaces[aIndex].mFontFace; } - aFound = false; return nullptr; } uint32_t -FontFaceSet::Length() +FontFaceSet::Size() { mPresContext->FlushUserFontSet(); @@ -346,6 +365,34 @@ FontFaceSet::Length() return std::min(total, INT32_MAX); } +FontFaceSetIterator* +FontFaceSet::Entries() +{ + return new FontFaceSetIterator(this, true); +} + +FontFaceSetIterator* +FontFaceSet::Values() +{ + return new FontFaceSetIterator(this, false); +} + +void +FontFaceSet::ForEach(JSContext* aCx, + FontFaceSetForEachCallback& aCallback, + JS::Handle aThisArg, + ErrorResult& aRv) +{ + JS::Rooted thisArg(aCx, aThisArg); + for (size_t i = 0; i < Size(); i++) { + FontFace* face = GetFontFaceAt(i); + aCallback.Call(thisArg, *face, *face, *this, aRv); + if (aRv.Failed()) { + return; + } + } +} + static PLDHashOperator DestroyIterator(nsPtrHashKey* aKey, void* aUserArg) { @@ -365,7 +412,7 @@ FontFaceSet::DestroyUserFontSet() mRuleFaces[i].mFontFace->SetUserFontEntry(nullptr); } for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { - mNonRuleFaces[i]->SetUserFontEntry(nullptr); + mNonRuleFaces[i].mFontFace->SetUserFontEntry(nullptr); } for (size_t i = 0; i < mUnavailableFaces.Length(); i++) { mUnavailableFaces[i]->SetUserFontEntry(nullptr); @@ -565,7 +612,7 @@ FontFaceSet::UpdateRules(const nsTArray& aRules) for (size_t i = 0, i_end = mNonRuleFaces.Length(); i < i_end; ++i) { // Do the same for the non rule backed FontFace objects. - InsertNonRuleFontFace(mNonRuleFaces[i], modified); + InsertNonRuleFontFace(mNonRuleFaces[i].mFontFace, modified); } // Remove any residual families that have no font entries (i.e., they were @@ -761,6 +808,9 @@ FontFaceSet::InsertRuleFontFace(FontFace* aFontFace, uint8_t aSheetType, FontFaceRecord rec; rec.mFontFace = aFontFace; rec.mSheetType = aSheetType; + rec.mLoadEventShouldFire = + aFontFace->Status() == FontFaceLoadStatus::Unloaded || + aFontFace->Status() == FontFaceLoadStatus::Loading; aFontFace->SetUserFontEntry(entry); @@ -1376,7 +1426,7 @@ FontFaceSet::UpdateHasLoadingFontFaces() } } for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { - if (mNonRuleFaces[i]->Status() == FontFaceLoadStatus::Loading) { + if (mNonRuleFaces[i].mFontFace->Status() == FontFaceLoadStatus::Loading) { mHasLoadingFontFaces = true; return; } @@ -1448,20 +1498,30 @@ FontFaceSet::CheckLoadingFinished() nsTArray failed; for (size_t i = 0; i < mRuleFaces.Length(); i++) { + if (!mRuleFaces[i].mLoadEventShouldFire) { + continue; + } FontFace* f = mRuleFaces[i].mFontFace; if (f->Status() == FontFaceLoadStatus::Loaded) { loaded.AppendElement(f); + mRuleFaces[i].mLoadEventShouldFire = false; } else if (f->Status() == FontFaceLoadStatus::Error) { failed.AppendElement(f); + mRuleFaces[i].mLoadEventShouldFire = false; } } for (size_t i = 0; i < mNonRuleFaces.Length(); i++) { - FontFace* f = mNonRuleFaces[i]; + if (!mNonRuleFaces[i].mLoadEventShouldFire) { + continue; + } + FontFace* f = mNonRuleFaces[i].mFontFace; if (f->Status() == FontFaceLoadStatus::Loaded) { loaded.AppendElement(f); + mNonRuleFaces[i].mLoadEventShouldFire = false; } else if (f->Status() == FontFaceLoadStatus::Error) { failed.AppendElement(f); + mNonRuleFaces[i].mLoadEventShouldFire = false; } } diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h index 00ac9052f0..fad3b7a27c 100644 --- a/layout/style/FontFaceSet.h +++ b/layout/style/FontFaceSet.h @@ -167,6 +167,8 @@ public: bool aWasAlternate, nsresult aStatus) override; + FontFace* GetFontFaceAt(uint32_t aIndex); + // -- Web IDL -------------------------------------------------------------- IMPL_EVENT_HANDLER(loading) @@ -185,8 +187,12 @@ public: void Clear(); bool Delete(FontFace& aFontFace, mozilla::ErrorResult& aRv); bool Has(FontFace& aFontFace); - FontFace* IndexedGetter(uint32_t aIndex, bool& aFound); - uint32_t Length(); + uint32_t Size(); + mozilla::dom::FontFaceSetIterator* Entries(); + mozilla::dom::FontFaceSetIterator* Values(); + void ForEach(JSContext* aCx, FontFaceSetForEachCallback& aCallback, + JS::Handle aThisArg, + mozilla::ErrorResult& aRv); private: ~FontFaceSet(); @@ -233,7 +239,12 @@ private: // accordingly. struct FontFaceRecord { nsRefPtr mFontFace; - uint8_t mSheetType; + uint8_t mSheetType; // only relevant for mRuleFaces entries + + // When true, indicates that when finished loading, the FontFace should be + // included in the subsequent loadingdone/loadingerror event fired at the + // FontFaceSet. + bool mLoadEventShouldFire; }; static already_AddRefed FindOrCreateUserFontEntryFromFontFace( @@ -300,8 +311,8 @@ private: nsTArray mRuleFaces; // The non rule backed FontFace objects that have been added to this - // FontFaceSet and their corresponding user font entries. - nsTArray> mNonRuleFaces; + // FontFaceSet. + nsTArray mNonRuleFaces; // The non rule backed FontFace objects that have not been added to // this FontFaceSet. diff --git a/layout/style/FontFaceSetIterator.cpp b/layout/style/FontFaceSetIterator.cpp new file mode 100644 index 0000000000..ce6d8bc0f9 --- /dev/null +++ b/layout/style/FontFaceSetIterator.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/dom/FontFaceSetIterator.h" + +namespace mozilla { +namespace dom { + +FontFaceSetIterator::FontFaceSetIterator(FontFaceSet* aFontFaceSet, + bool aIsKeyAndValue) + : mFontFaceSet(aFontFaceSet) + , mNextIndex(0) + , mIsKeyAndValue(aIsKeyAndValue) +{ + MOZ_COUNT_CTOR(FontFaceSetIterator); +} + +FontFaceSetIterator::~FontFaceSetIterator() +{ + MOZ_COUNT_DTOR(FontFaceSetIterator); +} + +bool +FontFaceSetIterator::WrapObject(JSContext* aCx, + JS::Handle aGivenProto, + JS::MutableHandle aReflector) +{ + return FontFaceSetIteratorBinding::Wrap(aCx, this, aGivenProto, aReflector); +} + +void +FontFaceSetIterator::Next(JSContext* aCx, FontFaceSetIteratorResult& aResult, + ErrorResult& aRv) +{ + if (!mFontFaceSet) { + aResult.mDone = true; + return; + } + + FontFace* face = mFontFaceSet->GetFontFaceAt(mNextIndex++); + + if (!face) { + aResult.mValue.setUndefined(); + aResult.mDone = true; + mFontFaceSet = nullptr; + return; + } + + JS::Rooted value(aCx); + if (!ToJSValue(aCx, face, &value)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (mIsKeyAndValue) { + JS::AutoValueArray<2> values(aCx); + values[0].set(value); + values[1].set(value); + + JS::Rooted array(aCx); + array = JS_NewArrayObject(aCx, values); + if (array) { + aResult.mValue.setObject(*array); + } + } else { + aResult.mValue = value; + } + + aResult.mDone = false; +} + +} // namespace dom +} // namespace mozilla diff --git a/layout/style/FontFaceSetIterator.h b/layout/style/FontFaceSetIterator.h new file mode 100644 index 0000000000..91ad787a7e --- /dev/null +++ b/layout/style/FontFaceSetIterator.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_dom_FontFaceSetIterator_h +#define mozilla_dom_FontFaceSetIterator_h + +#include "mozilla/dom/FontFaceSetBinding.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" + +namespace mozilla { +namespace dom { + +class FontFaceSetIterator final : public NonRefcountedDOMObject +{ +public: + FontFaceSetIterator(mozilla::dom::FontFaceSet* aFontFaceSet, + bool aIsKeyAndValue); + ~FontFaceSetIterator(); + + bool WrapObject(JSContext* aCx, + JS::Handle aGivenProto, + JS::MutableHandle aReflector); + + // WebIDL + void Next(JSContext* aCx, FontFaceSetIteratorResult& aResult, + mozilla::ErrorResult& aRv); + +private: + nsRefPtr mFontFaceSet; + uint32_t mNextIndex; + bool mIsKeyAndValue; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_FontFaceSetIterator_h) diff --git a/layout/style/GroupRule.h b/layout/style/GroupRule.h index dcaa9a4bc9..7cf00cb269 100644 --- a/layout/style/GroupRule.h +++ b/layout/style/GroupRule.h @@ -12,9 +12,9 @@ #define mozilla_css_GroupRule_h__ #include "mozilla/Attributes.h" +#include "mozilla/IncrementalClearCOMRuleArray.h" #include "mozilla/MemoryReporting.h" #include "mozilla/css/Rule.h" -#include "nsCOMArray.h" #include "nsAutoPtr.h" #include "nsCycleCollectionParticipant.h" @@ -57,7 +57,7 @@ public: int32_t StyleRuleCount() const { return mRules.Count(); } Rule* GetStyleRuleAt(int32_t aIndex) const; - typedef nsCOMArray::nsCOMArrayEnumFunc RuleEnumFunc; + typedef IncrementalClearCOMRuleArray::nsCOMArrayEnumFunc RuleEnumFunc; bool EnumerateRulesForwards(RuleEnumFunc aFunc, void * aData) const; /* @@ -80,7 +80,7 @@ public: CloneRuleInto(Rule* aRule, void* aArray) { nsRefPtr clone = aRule->Clone(); - static_cast*>(aArray)->AppendObject(clone); + static_cast(aArray)->AppendObject(clone); return true; } @@ -95,7 +95,7 @@ protected: uint32_t* _retval); nsresult DeleteRule(uint32_t aIndex); - nsCOMArray mRules; + IncrementalClearCOMRuleArray mRules; nsRefPtr mRuleCollection; // lazily constructed }; diff --git a/layout/style/IncrementalClearCOMRuleArray.cpp b/layout/style/IncrementalClearCOMRuleArray.cpp new file mode 100644 index 0000000000..00cb29b4b4 --- /dev/null +++ b/layout/style/IncrementalClearCOMRuleArray.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/IncrementalClearCOMRuleArray.h" + +#include "nsCycleCollector.h" +#include "mozilla/DeferredFinalize.h" +#include "nsTArray.h" +#include "nsCCUncollectableMarker.h" + +using namespace mozilla; + +typedef nsCOMArray RuleArray; +typedef nsTArray RuleArrayArray; + + +// These methods are based on those in DeferredFinalizerImpl. + +static void* +AppendRulesArrayPointer(void* aData, void* aObject) +{ + RuleArrayArray* rulesArray = static_cast(aData); + RuleArray* oldRules = static_cast(aObject); + + if (!rulesArray) { + rulesArray = new RuleArrayArray(); + } + + RuleArray* newRules = rulesArray->AppendElement(); + newRules->SwapElements(*oldRules); + + return rulesArray; +} + +// Remove up to |aSliceBudget| css::Rules from the arrays, starting at +// the end of the last array. +static bool +DeferredFinalizeRulesArray(uint32_t aSliceBudget, void* aData) +{ + MOZ_ASSERT(aSliceBudget > 0, "nonsensical/useless call with aSliceBudget == 0"); + RuleArrayArray* rulesArray = static_cast(aData); + + size_t newOuterLen = rulesArray->Length(); + + while (aSliceBudget > 0 && newOuterLen > 0) { + RuleArray& lastArray = rulesArray->ElementAt(newOuterLen - 1); + uint32_t innerLen = lastArray.Length(); + uint32_t currSlice = std::min(innerLen, aSliceBudget); + uint32_t newInnerLen = innerLen - currSlice; + lastArray.TruncateLength(newInnerLen); + aSliceBudget -= currSlice; + if (newInnerLen == 0) { + newOuterLen -= 1; + } + } + + rulesArray->TruncateLength(newOuterLen); + + if (newOuterLen == 0) { + delete rulesArray; + return true; + } + return false; +} + +void +IncrementalClearCOMRuleArray::Clear() +{ + // Destroy the array incrementally if it is long and we + // haven't started shutting down. + if (Length() > 10 && nsCCUncollectableMarker::sGeneration) { + DeferredFinalize(AppendRulesArrayPointer, DeferredFinalizeRulesArray, this); + } else { + nsCOMArray::Clear(); + } + MOZ_ASSERT(Length() == 0); +} diff --git a/layout/style/IncrementalClearCOMRuleArray.h b/layout/style/IncrementalClearCOMRuleArray.h new file mode 100644 index 0000000000..9934fd6837 --- /dev/null +++ b/layout/style/IncrementalClearCOMRuleArray.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_IncrementalClearCOMRuleArray_h +#define mozilla_IncrementalClearCOMRuleArray_h + +#include "nsCOMArray.h" + +namespace mozilla { +namespace css { +class Rule; +} // namespace css + +class IncrementalClearCOMRuleArray : public nsCOMArray +{ +public: + IncrementalClearCOMRuleArray() {} + ~IncrementalClearCOMRuleArray() { Clear(); } + + void Clear(); +}; + +} // namespace mozilla + +#endif /* !defined(mozilla_IncrementalClearCOMRuleArray_h) */ diff --git a/layout/style/generate-stylestructlist.py b/layout/style/generate-stylestructlist.py index 33239a3ad0..e9fa418ebc 100755 --- a/layout/style/generate-stylestructlist.py +++ b/layout/style/generate-stylestructlist.py @@ -9,33 +9,7 @@ # # 1. To generate code for each inherited style struct. # 2. To generate code for each reset style struct. -# 3. To generate a tree of nested if statements that can be used to run -# some code on each style struct. -# -# As an example, if we assume that we have only four style structs, the -# generated tree of nested if statements looks like this: -# -# if (STYLE_STRUCT_TEST < 4) { -# if (STYLE_STRUCT_TEST < 2) { -# if (STYLE_STRUCT_TEST == 0) { -# ... code for style struct with id 0 ... -# } else { -# ... code for style struct with id 1 ... -# } -# } else { -# if (STYLE_STRUCT_TEST == 2) { -# ... code for style struct with id 2 ... -# } else { -# ... code for style struct with id 3 ... -# } -# } -# } -# -# The TOPLEVELBRANCHES variable controls how widely we branch on the outermost -# if statement. In the example above, it splits the search space in 2, but with -# a larger number of style structs to test -- particularly when the number is -# closer to one power of two than the next higher one -- the average number of -# comparisons can be reduced by splitting the top level check into more than 2. +# 3. To generate the dependency of each style struct. from __future__ import print_function @@ -77,9 +51,6 @@ STYLE_STRUCTS = [("INHERITED",) + x for x in [ ("Column", "nullptr", NORMAL_DEP + LENGTH_DEP + COLOR_DEP), ]] -# How widely to branch on the outermost if statement. -TOPLEVELBRANCHES = 4 - # ---- Generate nsStyleStructList.h ---- @@ -116,42 +87,12 @@ for i in range(count): "the following structs:", " ".join(unsolved_items), file=sys.stderr) exit(1) -def nextPowerOf2(x): - return int(pow(2, math.ceil(math.log(x, 2)))) - def printEntry(header, i): print("STYLE_STRUCT_%s(%s, %s)" % STYLE_STRUCTS[i][:3], file=header) for dep in STYLE_STRUCTS[i][3]: print("STYLE_STRUCT_DEP(%s)" % (dep,), file=header) print("STYLE_STRUCT_END()", file=header) -def printTestTree(header, min, max, depth, branches): - indent = " " * depth - if min == count - 1 and max >= count: - print(" STYLE_STRUCT_TEST_CODE(%sNS_ASSERTION(STYLE_STRUCT_TEST == %d, \"out of range\");)" % (indent, min), file=header) - printEntry(header, min) - elif max - min == 2: - print(" STYLE_STRUCT_TEST_CODE(%sif (STYLE_STRUCT_TEST == %d) {)" % (indent, min), file=header) - printEntry(header, min) - print(" STYLE_STRUCT_TEST_CODE(%s} else {)" % indent, file=header) - printEntry(header, min + 1) - print(" STYLE_STRUCT_TEST_CODE(%s})" % indent, file=header) - elif min < count: - mid = min + (max - min) / branches - print(" STYLE_STRUCT_TEST_CODE(%sif (STYLE_STRUCT_TEST < %d) {)" % (indent, mid), file=header) - printTestTree(header, min, mid, depth + 1, 2) - for branch in range(1, branches): - lo = min + branch * (max - min) / branches - hi = min + (branch + 1) * (max - min) / branches - if lo >= count: - break - if branch == branches - 1 or hi >= count: - print(" STYLE_STRUCT_TEST_CODE(%s} else {)" % indent, file=header) - else: - print(" STYLE_STRUCT_TEST_CODE(%s} else if (STYLE_STRUCT_TEST < %d) {)" % (indent, hi), file=header) - printTestTree(header, lo, hi, depth + 1, 2) - print(" STYLE_STRUCT_TEST_CODE(%s})" % indent, file=header) - HEADER = """/* THIS FILE IS AUTOGENERATED BY generate-stylestructlist.py - DO NOT EDIT */ // IWYU pragma: private, include "nsStyleStructFwd.h" @@ -189,12 +130,6 @@ HEADER = """/* THIS FILE IS AUTOGENERATED BY generate-stylestructlist.py - DO NO #define UNDEF_STYLE_STRUCT_END #endif -#ifdef STYLE_STRUCT_TEST -#define STYLE_STRUCT_TEST_CODE(c) c -#else -#define STYLE_STRUCT_TEST_CODE(c) -#endif - // The inherited structs are listed before the Reset structs. // nsStyleStructID assumes this is the case, and callers other than // nsStyleStructFwd.h that want the structs in id-order just define @@ -221,11 +156,10 @@ FOOTER = """ #undef STYLE_STRUCT_END #undef UNDEF_STYLE_STRUCT_END #endif - -#undef STYLE_STRUCT_TEST_CODE """ def main(header): print(HEADER, file=header) - printTestTree(header, 0, nextPowerOf2(count), 0, TOPLEVELBRANCHES) + for i in range(count): + printEntry(header, i) print(FOOTER, file=header) diff --git a/layout/style/moz.build b/layout/style/moz.build index c457650815..f8d38703c7 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -84,6 +84,7 @@ EXPORTS.mozilla += [ 'CSSVariableDeclarations.h', 'CSSVariableResolver.h', 'CSSVariableValues.h', + 'IncrementalClearCOMRuleArray.h', 'StyleAnimationValue.h', ] @@ -93,6 +94,7 @@ EXPORTS.mozilla.dom += [ 'CSSValue.h', 'FontFace.h', 'FontFaceSet.h', + 'FontFaceSetIterator.h', 'MediaQueryList.h', ] @@ -120,7 +122,9 @@ UNIFIED_SOURCES += [ 'Declaration.cpp', 'ErrorReporter.cpp', 'FontFace.cpp', + 'FontFaceSetIterator.cpp', 'ImageLoader.cpp', + 'IncrementalClearCOMRuleArray.cpp', 'Loader.cpp', 'MediaQueryList.cpp', 'nsAnimationManager.cpp', diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 4902de41cb..e08a279650 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -332,6 +332,7 @@ CSS_KEY(landscape, landscape) CSS_KEY(large, large) CSS_KEY(larger, larger) CSS_KEY(last-baseline, last_baseline) +CSS_KEY(layout, layout) CSS_KEY(left, left) CSS_KEY(legacy, legacy) CSS_KEY(lighten, lighten) @@ -402,6 +403,7 @@ CSS_KEY(outside, outside) CSS_KEY(over, over) CSS_KEY(overlay, overlay) CSS_KEY(overline, overline) +CSS_KEY(paint, paint) CSS_KEY(padding-box, padding_box) CSS_KEY(painted, painted) CSS_KEY(pan-x, pan_x) @@ -516,6 +518,7 @@ CSS_KEY(sticky, sticky) CSS_KEY(stretch, stretch) CSS_KEY(stretch-to-fit, stretch_to_fit) CSS_KEY(stretched, stretched) +CSS_KEY(strict, strict) CSS_KEY(stroke, stroke) CSS_KEY(stroke-box, stroke_box) CSS_KEY(style, style) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 47d67ef380..0a3e712000 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -891,6 +891,7 @@ protected: // for 'clip' and '-moz-image-region' bool ParseRect(nsCSSProperty aPropID); bool ParseColumns(); + bool ParseContain(nsCSSValue& aValue); bool ParseContent(); bool ParseCounterData(nsCSSProperty aPropID); bool ParseCursor(); @@ -8096,17 +8097,17 @@ CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue) } } -// Parse a +// Parse a . bool CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue) { if (ParseNonNegativeVariant(aValue, - VARIANT_LPCALC | VARIANT_KEYWORD, + VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD, nsCSSProps::kGridTrackBreadthKTable)) { return true; } - // Attempt to parse (a dimension with the "fr" unit) + // Attempt to parse (a dimension with the "fr" unit). if (!GetToken(true)) { return false; } @@ -8120,17 +8121,16 @@ CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue) return true; } -// Parse a +// Parse a . CSSParseResult CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue) { - // Attempt to parse 'auto' or a single - if (ParseGridTrackBreadth(aValue) || - ParseVariant(aValue, VARIANT_AUTO, nullptr)) { + // Attempt to parse a single . + if (ParseGridTrackBreadth(aValue)) { return CSSParseResult::Ok; } - // Attempt to parse a minmax() function + // Attempt to parse a minmax() function. if (!GetToken(true)) { return CSSParseResult::NotFound; } @@ -10312,6 +10312,8 @@ CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue, return ParseTextOverflow(aValue); case eCSSProperty_touch_action: return ParseTouchAction(aValue); + case eCSSProperty_contain: + return ParseContain(aValue); default: MOZ_ASSERT(false, "should not reach here"); return false; @@ -12415,6 +12417,29 @@ CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue) maskEastAsian); } +bool +CSSParserImpl::ParseContain(nsCSSValue& aValue) +{ + if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { + return true; + } + static const int32_t maskContain[] = { MASK_END_VALUE }; + if (!ParseBitmaskValues(aValue, nsCSSProps::kContainKTable, maskContain)) { + return false; + } + if (aValue.GetIntValue() & NS_STYLE_CONTAIN_STRICT) { + if (aValue.GetIntValue() != NS_STYLE_CONTAIN_STRICT) { + // Disallow any other keywords in combination with 'strict'. + return false; + } + // Strict implies layout, style, and paint. + // However, for serialization purposes, we keep the strict bit around. + aValue.SetIntValue(NS_STYLE_CONTAIN_STRICT | + NS_STYLE_CONTAIN_ALL_BITS, eCSSUnit_Enumerated); + } + return true; +} + static const int32_t maskLigatures[] = { NS_FONT_VARIANT_LIGATURES_COMMON_MASK, NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK, @@ -13145,7 +13170,7 @@ CSSParserImpl::ParseListStyle() bool CSSParserImpl::ParseListStyleType(nsCSSValue& aValue) { - if (ParseVariant(aValue, VARIANT_INHERIT, nullptr)) { + if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_STRING, nullptr)) { return true; } diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 65b71926f1..0b3f201cb1 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -1487,6 +1487,18 @@ CSS_PROP_COLUMN( kBorderWidthKTable, CSS_PROP_NO_OFFSET, eStyleAnimType_Custom) +CSS_PROP_DISPLAY( + contain, + contain, + Contain, + CSS_PROPERTY_PARSE_VALUE | + CSS_PROPERTY_VALUE_PARSER_FUNCTION, + "layout.css.contain.enabled", + // Does not affect parsing, but is needed for tab completion in devtools: + VARIANT_HK | VARIANT_NONE, + kContainKTable, + CSS_PROP_NO_OFFSET, + eStyleAnimType_None) CSS_PROP_CONTENT( content, content, diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index ed5974a5d8..27173a50ce 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1522,6 +1522,15 @@ const KTableValue nsCSSProps::kMathDisplayKTable[] = { eCSSKeyword_UNKNOWN,-1 }; +const KTableValue nsCSSProps::kContainKTable[] = { + eCSSKeyword_none, NS_STYLE_CONTAIN_NONE, + eCSSKeyword_strict, NS_STYLE_CONTAIN_STRICT, + eCSSKeyword_layout, NS_STYLE_CONTAIN_LAYOUT, + eCSSKeyword_style, NS_STYLE_CONTAIN_STYLE, + eCSSKeyword_paint, NS_STYLE_CONTAIN_PAINT, + eCSSKeyword_UNKNOWN,-1 +}; + const KTableValue nsCSSProps::kContextOpacityKTable[] = { eCSSKeyword_context_fill_opacity, NS_STYLE_CONTEXT_FILL_OPACITY, eCSSKeyword_context_stroke_opacity, NS_STYLE_CONTEXT_STROKE_OPACITY, diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index 268748b1ed..9f15fef5bf 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -656,6 +656,7 @@ public: static const KTableValue kMaskTypeKTable[]; static const KTableValue kMathVariantKTable[]; static const KTableValue kMathDisplayKTable[]; + static const KTableValue kContainKTable[]; static const KTableValue kContextOpacityKTable[]; static const KTableValue kContextPatternKTable[]; static const KTableValue kObjectFitKTable[]; diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 179bcef6d3..61e25bb086 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1315,6 +1315,20 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult, aResult); break; + case eCSSProperty_contain: + if (intValue & NS_STYLE_CONTAIN_STRICT) { + NS_ASSERTION(intValue == (NS_STYLE_CONTAIN_STRICT | NS_STYLE_CONTAIN_ALL_BITS), + "contain: strict should imply contain: layout style paint"); + // Only output strict. + intValue = NS_STYLE_CONTAIN_STRICT; + } + nsStyleUtil::AppendBitmaskCSSValue(aProperty, + intValue, + NS_STYLE_CONTAIN_STRICT, + NS_STYLE_CONTAIN_PAINT, + aResult); + break; + case eCSSProperty_justify_items: AppendAlignJustifyValueToString(intValue, aResult); break; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 1f6bf2833d..c53da38787 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -51,9 +51,6 @@ using namespace mozilla; using namespace mozilla::dom; -typedef const nsStyleBackground::Position Position; -typedef const nsStyleBackground::Position::PositionCoord PositionCoord; -typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox; #if defined(DEBUG_bzbarsky) || defined(DEBUG_caillon) #define DEBUG_ComputedDOMStyle @@ -1274,7 +1271,8 @@ nsComputedDOMStyle::DoGetTransform() * using the named transforms. Until a real solution is found, we'll just * use this approach. */ - TransformReferenceBox refBox(mInnerFrame, nsSize(0, 0)); + nsStyleTransformMatrix::TransformReferenceBox refBox(mInnerFrame, + nsSize(0, 0)); bool dummy; gfx3DMatrix matrix = @@ -2091,8 +2089,9 @@ nsComputedDOMStyle::DoGetBackgroundOrigin() } void -nsComputedDOMStyle::SetValueToPositionCoord(const PositionCoord& aCoord, - nsROCSSPrimitiveValue* aValue) +nsComputedDOMStyle::SetValueToPositionCoord( + const nsStyleBackground::Position::PositionCoord& aCoord, + nsROCSSPrimitiveValue* aValue) { if (!aCoord.mHasPercent) { MOZ_ASSERT(aCoord.mPercent == 0.0f, @@ -2106,8 +2105,9 @@ nsComputedDOMStyle::SetValueToPositionCoord(const PositionCoord& aCoord, } void -nsComputedDOMStyle::SetValueToPosition(const Position& aPosition, - nsDOMCSSValueList* aValueList) +nsComputedDOMStyle::SetValueToPosition( + const nsStyleBackground::Position& aPosition, + nsDOMCSSValueList* aValueList) { nsROCSSPrimitiveValue* valX = new nsROCSSPrimitiveValue; aValueList->AppendCSSValue(valX); @@ -3121,15 +3121,17 @@ nsComputedDOMStyle::DoGetListStyleType() nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue; CounterStyle* style = StyleList()->GetCounterStyle(); AnonymousCounterStyle* anonymous = style->AsAnonymous(); + nsString tmp; if (!anonymous) { // want SetIdent nsString type; StyleList()->GetListStyleType(type); - nsString value; - nsStyleUtil::AppendEscapedCSSIdent(type, value); - val->SetString(value); + nsStyleUtil::AppendEscapedCSSIdent(type, tmp); + } else if (anonymous->IsSingleString()) { + const nsTArray& symbols = anonymous->GetSymbols(); + MOZ_ASSERT(symbols.Length() == 1); + nsStyleUtil::AppendEscapedCSSString(symbols[0], tmp); } else { - nsAutoString tmp; tmp.AppendLiteral("symbols("); uint8_t system = anonymous->GetSystem(); @@ -3153,8 +3155,8 @@ nsComputedDOMStyle::DoGetListStyleType() tmp.Append(' '); } tmp.Replace(tmp.Length() - 1, 1, char16_t(')')); - val->SetString(tmp); } + val->SetString(tmp); return val; } @@ -4028,6 +4030,31 @@ nsComputedDOMStyle::DoGetDisplay() return val; } +CSSValue* +nsComputedDOMStyle::DoGetContain() +{ + nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue; + + int32_t mask = StyleDisplay()->mContain; + + if (mask == 0) { + val->SetIdent(eCSSKeyword_none); + } else if (mask & NS_STYLE_CONTAIN_STRICT) { + NS_ASSERTION(mask == (NS_STYLE_CONTAIN_STRICT | NS_STYLE_CONTAIN_ALL_BITS), + "contain: strict should imply contain: layout style paint"); + val->SetIdent(eCSSKeyword_strict); + } else { + nsAutoString valueStr; + + nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_contain, + mask, NS_STYLE_CONTAIN_LAYOUT, + NS_STYLE_CONTAIN_PAINT, valueStr); + val->SetString(valueStr); + } + + return val; +} + CSSValue* nsComputedDOMStyle::DoGetPosition() { @@ -4994,7 +5021,7 @@ nsComputedDOMStyle::GetFrameBoundsWidthForTransform(nscoord& aWidth) AssertFlushedPendingReflows(); - aWidth = TransformReferenceBox(mInnerFrame).Width(); + aWidth = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Width(); return true; } @@ -5008,7 +5035,7 @@ nsComputedDOMStyle::GetFrameBoundsHeightForTransform(nscoord& aHeight) AssertFlushedPendingReflows(); - aHeight = TransformReferenceBox(mInnerFrame).Height(); + aHeight = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Height(); return true; } diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index 29519c7123..9932d0eb2a 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -392,6 +392,7 @@ private: mozilla::dom::CSSValue* DoGetClear(); mozilla::dom::CSSValue* DoGetFloat(); mozilla::dom::CSSValue* DoGetDisplay(); + mozilla::dom::CSSValue* DoGetContain(); mozilla::dom::CSSValue* DoGetPosition(); mozilla::dom::CSSValue* DoGetClip(); mozilla::dom::CSSValue* DoGetImageOrientation(); diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h index 923b67d725..2377336df3 100644 --- a/layout/style/nsComputedDOMStylePropertyList.h +++ b/layout/style/nsComputedDOMStylePropertyList.h @@ -110,6 +110,7 @@ COMPUTED_STYLE_PROP(column_rule_color, ColumnRuleColor) COMPUTED_STYLE_PROP(column_rule_style, ColumnRuleStyle) COMPUTED_STYLE_PROP(column_rule_width, ColumnRuleWidth) COMPUTED_STYLE_PROP(column_width, ColumnWidth) +COMPUTED_STYLE_PROP(contain, Contain) COMPUTED_STYLE_PROP(content, Content) COMPUTED_STYLE_PROP(counter_increment, CounterIncrement) COMPUTED_STYLE_PROP(counter_reset, CounterReset) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index ca980c036b..979054c018 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1394,7 +1394,6 @@ SetFactor(const nsCSSValue& aValue, float& aField, bool& aCanStoreInRuleTree, NS_NOTREACHED("SetFactor: inappropriate unit"); } -// Overloaded new operator that allocates from a presShell arena. void* nsRuleNode::operator new(size_t sz, nsPresContext* aPresContext) CPP_THROW_NEW { @@ -1554,20 +1553,11 @@ nsRuleNode::Transition(nsIStyleRule* aRule, uint8_t aLevel, else { next = entry->mRuleNode = new (mPresContext) nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule); - if (!next) { - PL_DHashTableRawRemove(ChildrenHash(), entry); - NS_WARNING("out of memory"); - return this; - } } } else if (!next) { // Create the new entry in our list. next = new (mPresContext) nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule); - if (!next) { - NS_WARNING("out of memory"); - return this; - } next->mNextSibling = ChildrenList(); SetChildrenList(next); } @@ -2398,18 +2388,19 @@ nsRuleNode::WalkRuleTree(const nsStyleStructID aSID, return SetDefaultOnRoot(aSID, aContext); } - // We need to compute the data from the information that the rules specified. - const void* res; -#define STYLE_STRUCT_TEST aSID -#define STYLE_STRUCT(name, checkdata_cb) \ - res = Compute##name##Data(startStruct, &ruleData, aContext, \ - highestNode, detail, ruleData.mCanStoreInRuleTree); + typedef const void* (nsRuleNode::*ComputeFunc)(void*, const nsRuleData*, + nsStyleContext*, nsRuleNode*, + RuleDetail, const bool); + static const ComputeFunc sComputeFuncs[] = { +#define STYLE_STRUCT(name, checkdata_cb) &nsRuleNode::Compute##name##Data, #include "nsStyleStructList.h" #undef STYLE_STRUCT -#undef STYLE_STRUCT_TEST + }; - // Now return the result. - return res; + // We need to compute the data from the information that the rules specified. + return (this->*sComputeFuncs[aSID])(startStruct, &ruleData, aContext, + highestNode, detail, + ruleData.mCanStoreInRuleTree); } const void* @@ -5259,6 +5250,12 @@ nsRuleNode::ComputeDisplayData(void* aStartStruct, parentDisplay->mDisplay, NS_STYLE_DISPLAY_INLINE, 0, 0, 0, 0); + // contain: none, enum, inherit, initial + SetDiscrete(*aRuleData->ValueForContain(), display->mContain, canStoreInRuleTree, + SETDSC_ENUMERATED | SETDSC_NONE | SETDSC_UNSET_INITIAL, + parentDisplay->mContain, + NS_STYLE_CONTAIN_NONE, 0, NS_STYLE_CONTAIN_NONE, 0, 0); + // mix-blend-mode: enum, inherit, initial SetDiscrete(*aRuleData->ValueForMixBlendMode(), display->mMixBlendMode, canStoreInRuleTree, @@ -7215,6 +7212,13 @@ nsRuleNode::ComputeListData(void* aStartStruct, list->SetListStyleType(typeIdent, mPresContext); break; } + case eCSSUnit_String: { + nsString str; + typeValue->GetStringValue(str); + list->SetListStyleType(NS_LITERAL_STRING(""), + new AnonymousCounterStyle(str)); + break; + } case eCSSUnit_Enumerated: { // For compatibility with html attribute map. // This branch should never be called for value from CSS. @@ -7242,9 +7246,9 @@ nsRuleNode::ComputeListData(void* aStartStruct, break; } case eCSSUnit_Symbols: - list->SetListStyleType(NS_LITERAL_STRING(""), - mPresContext->CounterStyleManager()-> - BuildCounterStyle(typeValue->GetArrayValue())); + list->SetListStyleType( + NS_LITERAL_STRING(""), + new AnonymousCounterStyle(typeValue->GetArrayValue())); break; case eCSSUnit_Null: break; @@ -7343,6 +7347,8 @@ SetGridTrackBreadth(const nsCSSValue& aValue, nsCSSUnit unit = aValue.GetUnit(); if (unit == eCSSUnit_FlexFraction) { aResult.SetFlexFractionValue(aValue.GetFloatValue()); + } else if (unit == eCSSUnit_Auto) { + aResult.SetAutoValue(); } else { MOZ_ASSERT(unit != eCSSUnit_Inherit && unit != eCSSUnit_Unset, "Unexpected value that would use dummyParentCoord"); @@ -7370,12 +7376,6 @@ SetGridTrackSize(const nsCSSValue& aValue, aStyleContext, aPresContext, aCanStoreInRuleTree); SetGridTrackBreadth(func->Item(2), aResultMax, aStyleContext, aPresContext, aCanStoreInRuleTree); - } else if (aValue.GetUnit() == eCSSUnit_Auto) { - // 'auto' computes to 'minmax(min-content, max-content)' - aResultMin.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT, - eStyleUnit_Enumerated); - aResultMax.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT, - eStyleUnit_Enumerated); } else { // A single , // specifies identical min and max sizing functions. @@ -7409,12 +7409,10 @@ SetGridAutoColumnsRows(const nsCSSValue& aValue, case eCSSUnit_Initial: case eCSSUnit_Unset: // The initial value is 'auto', - // which computes to 'minmax(min-content, max-content)'. + // which computes to 'minmax(auto, auto)'. // (Explicitly-specified 'auto' values are handled in SetGridTrackSize.) - aResultMin.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT, - eStyleUnit_Enumerated); - aResultMax.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT, - eStyleUnit_Enumerated); + aResultMin.SetAutoValue(); + aResultMax.SetAutoValue(); break; default: diff --git a/layout/style/nsRuleNode.h b/layout/style/nsRuleNode.h index c651757f65..e031b5d724 100644 --- a/layout/style/nsRuleNode.h +++ b/layout/style/nsRuleNode.h @@ -11,6 +11,7 @@ #ifndef nsRuleNode_h___ #define nsRuleNode_h___ +#include "mozilla/RangedArray.h" #include "nsPresContext.h" #include "nsStyleStruct.h" @@ -24,29 +25,11 @@ class nsCSSValue; class nsStyleCoord; struct nsCSSValuePairList; -template -class FixedStyleStructArray -{ -private: - void* mArray[Count]; -public: - void*& operator[](nsStyleStructID aIndex) { - MOZ_ASSERT(MinIndex <= aIndex && aIndex < (MinIndex + Count), - "out of range"); - return mArray[aIndex - MinIndex]; - } - - const void* operator[](nsStyleStructID aIndex) const { - MOZ_ASSERT(MinIndex <= aIndex && aIndex < (MinIndex + Count), - "out of range"); - return mArray[aIndex - MinIndex]; - } -}; - struct nsInheritedStyleData { - FixedStyleStructArray mStyleStructs; + mozilla::RangedArray mStyleStructs; void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW { return aContext->PresShell()-> @@ -83,8 +66,9 @@ struct nsInheritedStyleData struct nsResetStyleData { - FixedStyleStructArray mStyleStructs; + mozilla::RangedArray mStyleStructs; nsResetStyleData() { @@ -410,7 +394,7 @@ private: uint32_t mRefCnt; public: - // Overloaded new operator that allocates from a presShell arena. + // Infallible overloaded new operator that allocates from a presShell arena. void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW; void Destroy() { DestroyInternal(nullptr); } @@ -667,6 +651,7 @@ private: ~nsRuleNode(); public: + // This is infallible; it will never return nullptr. static nsRuleNode* CreateRootNode(nsPresContext* aPresContext); static void EnsureBlockDisplay(uint8_t& display, diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index 4d4f85ba79..e44b64ff7a 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -448,6 +448,19 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #define NS_STYLE_DISPLAY_RUBY_TEXT_CONTAINER 37 #define NS_STYLE_DISPLAY_CONTENTS 38 +// See nsStyleDisplay +// If these are re-ordered, nsComputedDOMStyle::DoGetContain() and +// nsCSSValue::AppendToString() must be updated. +#define NS_STYLE_CONTAIN_NONE 0 +#define NS_STYLE_CONTAIN_STRICT 0x1 +#define NS_STYLE_CONTAIN_LAYOUT 0x2 +#define NS_STYLE_CONTAIN_STYLE 0x4 +#define NS_STYLE_CONTAIN_PAINT 0x8 +// NS_STYLE_CONTAIN_ALL_BITS does not correspond to a keyword. +#define NS_STYLE_CONTAIN_ALL_BITS (NS_STYLE_CONTAIN_LAYOUT | \ + NS_STYLE_CONTAIN_STYLE | \ + NS_STYLE_CONTAIN_PAINT) + // Shared constants for all align/justify properties (nsStylePosition): #define NS_STYLE_ALIGN_AUTO 0 #define NS_STYLE_ALIGN_START 1 diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp index c006a9febf..747c2925ba 100644 --- a/layout/style/nsStyleSet.cpp +++ b/layout/style/nsStyleSet.cpp @@ -218,10 +218,7 @@ nsStyleSet::BeginReconstruct() NS_ASSERTION(mRuleTree, "Reconstructing before first construction?"); // Create a new rule tree root - nsRuleNode* newTree = - nsRuleNode::CreateRootNode(mRuleTree->PresContext()); - if (!newTree) - return NS_ERROR_OUT_OF_MEMORY; + nsRuleNode* newTree = nsRuleNode::CreateRootNode(mRuleTree->PresContext()); // Save the old rule tree so we can destroy it later if (!mOldRuleTrees.AppendElement(mRuleTree)) { diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 8e5430b1a6..437cbeab65 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -32,6 +32,8 @@ #include "nsIDocument.h" #include +using namespace mozilla; + static_assert((((1 << nsStyleStructID_Length) - 1) & ~(NS_STYLE_INHERIT_MASK)) == 0, "Not enough bits in NS_STYLE_INHERIT_MASK"); @@ -1411,15 +1413,11 @@ nsStylePosition::nsStylePosition(void) mFlexBasis.SetAutoValue(); // The initial value of grid-auto-columns and grid-auto-rows is 'auto', - // which computes to 'minmax(min-content, max-content)'. - mGridAutoColumnsMin.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT, - eStyleUnit_Enumerated); - mGridAutoColumnsMax.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT, - eStyleUnit_Enumerated); - mGridAutoRowsMin.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MIN_CONTENT, - eStyleUnit_Enumerated); - mGridAutoRowsMax.SetIntValue(NS_STYLE_GRID_TRACK_BREADTH_MAX_CONTENT, - eStyleUnit_Enumerated); + // which computes to 'minmax(auto, auto)'. + mGridAutoColumnsMin.SetAutoValue(); + mGridAutoColumnsMax.SetAutoValue(); + mGridAutoRowsMin.SetAutoValue(); + mGridAutoRowsMax.SetAutoValue(); mGridAutoFlow = NS_STYLE_GRID_AUTO_FLOW_ROW; mBoxSizing = NS_STYLE_BOX_SIZING_CONTENT; @@ -2605,6 +2603,7 @@ nsStyleDisplay::nsStyleDisplay() mAppearance = NS_THEME_NONE; mDisplay = NS_STYLE_DISPLAY_INLINE; mOriginalDisplay = mDisplay; + mContain = NS_STYLE_CONTAIN_NONE; mPosition = NS_STYLE_POSITION_STATIC; mFloats = NS_STYLE_FLOAT_NONE; mOriginalFloats = mFloats; @@ -2670,6 +2669,7 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource) , mOpacity(aSource.mOpacity) , mDisplay(aSource.mDisplay) , mOriginalDisplay(aSource.mOriginalDisplay) + , mContain(aSource.mContain) , mAppearance(aSource.mAppearance) , mPosition(aSource.mPosition) , mFloats(aSource.mFloats) @@ -2733,6 +2733,7 @@ nsChangeHint nsStyleDisplay::CalcDifference(const nsStyleDisplay& aOther) const if (!EqualURIs(mBinding, aOther.mBinding) || mPosition != aOther.mPosition || mDisplay != aOther.mDisplay + || mContain != aOther.mContain || (mFloats == NS_STYLE_FLOAT_NONE) != (aOther.mFloats == NS_STYLE_FLOAT_NONE) || mOverflowX != aOther.mOverflowX || mOverflowY != aOther.mOverflowY diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 7f6d6fa913..02ab4e0dfc 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1927,6 +1927,7 @@ struct nsStyleDisplay { uint8_t mOriginalDisplay; // [reset] saved mDisplay for position:absolute/fixed // and float:left/right; otherwise equal // to mDisplay + uint8_t mContain; // [reset] see nsStyleConsts.h NS_STYLE_CONTAIN_* uint8_t mAppearance; // [reset] uint8_t mPosition; // [reset] see nsStyleConsts.h uint8_t mFloats; // [reset] see nsStyleConsts.h NS_STYLE_FLOAT_* diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp index a990046806..05d04a98d8 100644 --- a/layout/style/nsStyleTransformMatrix.cpp +++ b/layout/style/nsStyleTransformMatrix.cpp @@ -227,8 +227,7 @@ ProcessMatrix3D(gfx3DMatrix& aMatrix, &aRefBox, &TransformReferenceBox::Height); temp._43 = ProcessTranslatePart(aData->Item(15), aContext, aPresContext, aCanStoreInRuleTree, - &aRefBox, &TransformReferenceBox::Height); - // XXXjwatt why are we using TransformReferenceBox::Height for the z compontent?! + nullptr); aMatrix.PreMultiply(temp); } diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 604982da73..5129e9c09c 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -2764,6 +2764,7 @@ var gCSSProperties = { subproperties: [ "list-style-type", "list-style-position", "list-style-image" ], initial_values: [ "outside", "disc", "disc outside", "outside disc", "disc none", "none disc", "none disc outside", "none outside disc", "disc none outside", "disc outside none", "outside none disc", "outside disc none" ], other_values: [ "inside none", "none inside", "none none inside", "square", "none", "none none", "outside none none", "none outside none", "none none outside", "none outside", "outside none", "outside outside", "outside inside", "\\32 style", "\\32 style inside", + '"-"', "'-'", "inside '-'", "'-' outside", "none '-'", "inside none '-'", "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", "symbols(cyclic \"*\" \"\\2020\" \"\\2021\" \"\\A7\")", "inside symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", @@ -2841,6 +2842,7 @@ var gCSSProperties = { "-moz-ethiopic-halehame-am", "-moz-ethiopic-halehame-ti-er", "-moz-ethiopic-halehame-ti-et", "other-style", "inside", "outside", "\\32 style", + '"-"', "'-'", "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", "symbols(cyclic '*' '\\2020' '\\2021' '\\A7')" ], @@ -5835,6 +5837,8 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "max-content", "calc(20px + 10%)", "minmax(20px, max-content)", + "minmax(min-content, auto)", + "minmax(auto, max-content)", "m\\69nmax(20px, 4Fr)", "MinMax(min-content, calc(20px + 10%))", ], @@ -5851,7 +5855,6 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "mİnmax(20px, 100px)", "minmax(20px, 100px, 200px)", "maxmin(100px, 20px)", - "minmax(min-content, auto)", "minmax(min-content, minmax(30px, max-content))", ] }; @@ -5876,6 +5879,8 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "(normal) 40px () auto ( ) 12%", "(foo) 40px min-content ( bar ) calc(20px + 10%) max-content", "40px min-content calc(20px + 10%) max-content", + "minmax(min-content, auto)", + "minmax(auto, max-content)", "m\\69nmax(20px, 4Fr)", "40px MinMax(min-content, calc(20px + 10%)) max-content", "40px 2em", @@ -5922,7 +5927,6 @@ if (SpecialPowers.getBoolPref("layout.css.grid.enabled")) { "mİnmax(20px, 100px)", "minmax(20px, 100px, 200px)", "maxmin(100px, 20px)", - "minmax(min-content, auto)", "minmax(min-content, minmax(30px, max-content))", "repeat(0, 20px)", "repeat(-3, 20px)", @@ -6225,6 +6229,46 @@ if (SpecialPowers.getBoolPref("layout.css.display-contents.enabled")) { gCSSProperties["display"].other_values.push("contents"); } +if (SpecialPowers.getBoolPref("layout.css.contain.enabled")) { + gCSSProperties["contain"] = { + domProp: "contain", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "strict", + "layout", + "style", + "layout style", + "style layout", + "paint", + "layout paint", + "paint layout", + "style paint", + "paint style", + "layout style paint", + "layout paint style", + "style paint layout", + "paint style layout", + ], + invalid_values: [ + "none strict", + "strict layout", + "strict layout style", + "layout strict", + "layout style strict", + "layout style paint strict", + "paint strict", + "style strict", + "paint paint", + "strict strict", + "auto", + "10px", + "0", + ] + }; +} + if (SpecialPowers.getBoolPref("layout.css.image-orientation.enabled")) { gCSSProperties["image-orientation"] = { domProp: "imageOrientation", diff --git a/layout/style/test/test_grid_container_shorthands.html b/layout/style/test/test_grid_container_shorthands.html index fa12971d51..992e26629b 100644 --- a/layout/style/test/test_grid_container_shorthands.html +++ b/layout/style/test/test_grid_container_shorthands.html @@ -18,8 +18,8 @@ var initial_values = { gridTemplateRows: "none", gridAutoFlow: "row", // Computed value for 'auto' - gridAutoColumns: "minmax(min-content, max-content)", - gridAutoRows: "minmax(min-content, max-content)", + gridAutoColumns: "auto", + gridAutoRows: "auto", }; // For various specified values of the grid-template shorthand, @@ -106,24 +106,24 @@ var grid_template_test_cases = [ { specified: "'fizz'", gridTemplateAreas: "\"fizz\"", - gridTemplateRows: "minmax(min-content, max-content)", + gridTemplateRows: "auto", }, { specified: "(bar) 'fizz'", gridTemplateAreas: "\"fizz\"", - gridTemplateRows: "(bar) minmax(min-content, max-content)", + gridTemplateRows: "(bar) auto", }, { specified: "(foo) 40px / 'fizz'", gridTemplateAreas: "\"fizz\"", gridTemplateColumns: "(foo) 40px", - gridTemplateRows: "minmax(min-content, max-content)", + gridTemplateRows: "auto", }, { specified: "(foo) 40px / (bar) 'fizz'", gridTemplateAreas: "\"fizz\"", gridTemplateColumns: "(foo) 40px", - gridTemplateRows: "(bar) minmax(min-content, max-content)", + gridTemplateRows: "(bar) auto", }, { specified: "(foo) 40px / 'fizz' 100px", @@ -195,7 +195,7 @@ grid_test_cases = grid_template_test_cases.concat([ { specified: "column dense auto", gridAutoFlow: "column dense", - gridAutoColumns: "minmax(min-content, max-content)", + gridAutoColumns: "auto", }, { specified: "dense row minmax(min-content, 2fr)", diff --git a/mfbt/RangedArray.h b/mfbt/RangedArray.h new file mode 100644 index 0000000000..8ef2dd02a5 --- /dev/null +++ b/mfbt/RangedArray.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* + * A compile-time constant-length array, with bounds-checking assertions -- but + * unlike mozilla::Array, with indexes biased by a constant. + * + * Thus where mozilla::Array is a three-element array indexed by [0, 3), + * mozilla::RangedArray is a three-element array indexed by [8, 11). + */ + +#ifndef mozilla_RangedArray_h +#define mozilla_RangedArray_h + +#include "mozilla/Array.h" + +namespace mozilla { + +template +class RangedArray +{ +public: + T& operator[](size_t aIndex) + { + MOZ_ASSERT(aIndex == MinIndex || aIndex > MinIndex); + return mArr[aIndex - MinIndex]; + } + + const T& operator[](size_t aIndex) const + { + MOZ_ASSERT(aIndex == MinIndex || aIndex > MinIndex); + return mArr[aIndex - MinIndex]; + } + +private: + Array mArr; +}; + +} // namespace mozilla + +#endif // mozilla_RangedArray_h diff --git a/mfbt/moz.build b/mfbt/moz.build index 7561723dda..4bf2b5f9d5 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -68,6 +68,7 @@ EXPORTS.mozilla = [ 'PodOperations.h', 'Poison.h', 'Range.h', + 'RangedArray.h', 'RangedPtr.h', 'RefCounted.h', 'RefCountType.h', diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 58d20fe5f7..2e633c221c 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2561,6 +2561,9 @@ pref("layout.css.overflow-clip-box.enabled", false); // Is support for CSS grid enabled? pref("layout.css.grid.enabled", false); +// Is support for CSS contain enabled? +pref("layout.css.contain.enabled", false); + // Is support for CSS Ruby enabled? // // When this pref is removed, make sure that the pref callback registration diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index f1db2b62cb..62b8e4b4ce 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -147,6 +147,9 @@ user_pref("layout.css.report_errors", true); // Enable CSS Grid for testing user_pref("layout.css.grid.enabled", true); +// Enable CSS 'contain' for testing +user_pref("layout.css.contain.enabled", true); + // Enable CSS object-fit & object-position for testing user_pref("layout.css.object-fit-and-position.enabled", true); diff --git a/toolkit/devtools/styleinspector/test/browser_ruleview_completion-new-property_02.js b/toolkit/devtools/styleinspector/test/browser_ruleview_completion-new-property_02.js index 72e1dfe367..dc058e98b1 100644 --- a/toolkit/devtools/styleinspector/test/browser_ruleview_completion-new-property_02.js +++ b/toolkit/devtools/styleinspector/test/browser_ruleview_completion-new-property_02.js @@ -25,7 +25,7 @@ let testData = [ ["VK_TAB", {shiftKey: true}, "display", -1, 0], ["VK_BACK_SPACE", {}, "", -1, 0], ["c", {}, "caption-side", 0, 10], - ["o", {}, "color", 0, 6], + ["o", {}, "color", 0, 7], ["VK_TAB", {}, "none", -1, 0], ["r", {}, "rebeccapurple", 0, 6], ["VK_DOWN", {}, "red", 1, 6], diff --git a/xpcom/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h index 2f3a663e31..f70af4ce24 100644 --- a/xpcom/glue/nsBaseHashtable.h +++ b/xpcom/glue/nsBaseHashtable.h @@ -167,10 +167,17 @@ public: */ uint32_t EnumerateRead(EnumReadFunction aEnumFunc, void* aUserArg) const { - s_EnumReadArgs enumData = { aEnumFunc, aUserArg }; - return PL_DHashTableEnumerate(const_cast(&this->mTable), - s_EnumReadStub, - &enumData); + uint32_t n = 0; + for (auto iter = this->mTable.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + PLDHashOperator op = aEnumFunc(entry->GetKey(), entry->mData, aUserArg); + n++; + MOZ_ASSERT(!(op & PL_DHASH_REMOVE)); + if (op & PL_DHASH_STOP) { + break; + } + } + return n; } /** @@ -196,10 +203,19 @@ public: */ uint32_t Enumerate(EnumFunction aEnumFunc, void* aUserArg) { - s_EnumArgs enumData = { aEnumFunc, aUserArg }; - return PL_DHashTableEnumerate(&this->mTable, - s_EnumStub, - &enumData); + uint32_t n = 0; + for (auto iter = this->mTable.RemovingIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + PLDHashOperator op = aEnumFunc(entry->GetKey(), entry->mData, aUserArg); + n++; + if (op & PL_DHASH_REMOVE) { + iter.Remove(); + } + if (op & PL_DHASH_STOP) { + break; + } + } + return n; } /** @@ -273,33 +289,6 @@ public: #endif protected: - /** - * used internally during EnumerateRead. Allocated on the stack. - * @param func the enumerator passed to EnumerateRead - * @param userArg the userArg passed to EnumerateRead - */ - struct s_EnumReadArgs - { - EnumReadFunction func; - void* userArg; - }; - - static PLDHashOperator s_EnumReadStub(PLDHashTable* aTable, - PLDHashEntryHdr* aHdr, - uint32_t aNumber, - void* aArg); - - struct s_EnumArgs - { - EnumFunction func; - void* userArg; - }; - - static PLDHashOperator s_EnumStub(PLDHashTable* aTable, - PLDHashEntryHdr* aHdr, - uint32_t aNumber, - void* aArg); - struct s_SizeOfArgs { SizeOfEntryExcludingThisFun func; @@ -374,37 +363,6 @@ nsBaseHashtableET::~nsBaseHashtableET() // nsBaseHashtable definitions // -template -PLDHashOperator -nsBaseHashtable::s_EnumReadStub( - PLDHashTable* aTable, PLDHashEntryHdr* aHdr, uint32_t aNumber, void* aArg) -{ - EntryType* ent = static_cast(aHdr); - s_EnumReadArgs* eargs = (s_EnumReadArgs*)aArg; - - PLDHashOperator res = (eargs->func)(ent->GetKey(), ent->mData, eargs->userArg); - - NS_ASSERTION(!(res & PL_DHASH_REMOVE), - "PL_DHASH_REMOVE return during const enumeration; ignoring."); - - if (res & PL_DHASH_STOP) { - return PL_DHASH_STOP; - } - - return PL_DHASH_NEXT; -} - -template -PLDHashOperator -nsBaseHashtable::s_EnumStub( - PLDHashTable* aTable, PLDHashEntryHdr* aHdr, uint32_t aNumber, void* aArg) -{ - EntryType* ent = static_cast(aHdr); - s_EnumArgs* eargs = (s_EnumArgs*)aArg; - - return (eargs->func)(ent->GetKey(), ent->mData, eargs->userArg); -} - template size_t nsBaseHashtable::s_SizeOfStub( diff --git a/xpcom/glue/nsTHashtable.h b/xpcom/glue/nsTHashtable.h index e4c9facac9..ad15f6a700 100644 --- a/xpcom/glue/nsTHashtable.h +++ b/xpcom/glue/nsTHashtable.h @@ -207,8 +207,19 @@ public: */ uint32_t EnumerateEntries(Enumerator aEnumFunc, void* aUserArg) { - s_EnumArgs args = { aEnumFunc, aUserArg }; - return PL_DHashTableEnumerate(&mTable, s_EnumStub, &args); + uint32_t n = 0; + for (auto iter = mTable.RemovingIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + PLDHashOperator op = aEnumFunc(entry, aUserArg); + n++; + if (op & PL_DHASH_REMOVE) { + iter.Remove(); + } + if (op & PL_DHASH_STOP) { + break; + } + } + return n; } /** @@ -324,23 +335,6 @@ protected: static void s_InitEntry(PLDHashEntryHdr* aEntry, const void* aKey); - /** - * passed internally during enumeration. Allocated on the stack. - * - * @param userFunc the Enumerator function passed to - * EnumerateEntries by the client - * @param userArg the userArg passed unaltered - */ - struct s_EnumArgs - { - Enumerator userFunc; - void* userArg; - }; - - static PLDHashOperator s_EnumStub(PLDHashTable* aTable, - PLDHashEntryHdr* aEntry, - uint32_t aNumber, void* aArg); - /** * passed internally during sizeOf counting. Allocated on the stack. * @@ -473,19 +467,6 @@ nsTHashtable::s_InitEntry(PLDHashEntryHdr* aEntry, new (aEntry) EntryType(reinterpret_cast(aKey)); } -template -PLDHashOperator -nsTHashtable::s_EnumStub(PLDHashTable* aTable, - PLDHashEntryHdr* aEntry, - uint32_t aNumber, - void* aArg) -{ - // dereferences the function-pointer to the user's enumeration function - return (*reinterpret_cast(aArg)->userFunc)( - static_cast(aEntry), - reinterpret_cast(aArg)->userArg); -} - template size_t nsTHashtable::s_SizeOfStub(PLDHashEntryHdr* aEntry, diff --git a/xpcom/glue/pldhash.cpp b/xpcom/glue/pldhash.cpp index a2b81d66d2..e579be3838 100644 --- a/xpcom/glue/pldhash.cpp +++ b/xpcom/glue/pldhash.cpp @@ -20,56 +20,51 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/ChaosMode.h" -#ifdef PL_DHASHMETER -# define METER(x) x -#else -# define METER(x) /* nothing */ -#endif +using namespace mozilla; -/* - * The following DEBUG-only code is used to assert that calls to one of - * table->mOps or to an enumerator do not cause re-entry into a call that - * can mutate the table. - */ #ifdef DEBUG -/* - * Most callers that assert about the recursion level don't care about - * this magical value because they are asserting that mutation is - * allowed (and therefore the level is 0 or 1, depending on whether they - * incremented it). - * - * Only the destructor needs to allow this special value. - */ -#define IMMUTABLE_RECURSION_LEVEL UINT32_MAX +class AutoReadOp +{ + Checker& mChk; +public: + explicit AutoReadOp(Checker& aChk) : mChk(aChk) { mChk.StartReadOp(); } + ~AutoReadOp() { mChk.EndReadOp(); } +}; -#define RECURSION_LEVEL_SAFE_TO_FINISH(table_) \ - (table_->mRecursionLevel == 0 || \ - table_->mRecursionLevel == IMMUTABLE_RECURSION_LEVEL) +class AutoWriteOp +{ + Checker& mChk; +public: + explicit AutoWriteOp(Checker& aChk) : mChk(aChk) { mChk.StartWriteOp(); } + ~AutoWriteOp() { mChk.EndWriteOp(); } +}; -#define INCREMENT_RECURSION_LEVEL(table_) \ - do { \ - if (table_->mRecursionLevel != IMMUTABLE_RECURSION_LEVEL) { \ - const uint32_t oldRecursionLevel = table_->mRecursionLevel++; \ - MOZ_ASSERT(oldRecursionLevel < IMMUTABLE_RECURSION_LEVEL - 1); \ - } \ - } while(0) -#define DECREMENT_RECURSION_LEVEL(table_) \ - do { \ - if (table_->mRecursionLevel != IMMUTABLE_RECURSION_LEVEL) { \ - const uint32_t oldRecursionLevel = table_->mRecursionLevel--; \ - MOZ_ASSERT(oldRecursionLevel > 0); \ - } \ - } while(0) +class AutoIteratorRemovalOp +{ + Checker& mChk; +public: + explicit AutoIteratorRemovalOp(Checker& aChk) + : mChk(aChk) + { + mChk.StartIteratorRemovalOp(); + } + ~AutoIteratorRemovalOp() { mChk.EndIteratorRemovalOp(); } +}; -#else +class AutoDestructorOp +{ + Checker& mChk; +public: + explicit AutoDestructorOp(Checker& aChk) + : mChk(aChk) + { + mChk.StartDestructorOp(); + } + ~AutoDestructorOp() { mChk.EndDestructorOp(); } +}; -#define INCREMENT_RECURSION_LEVEL(table_) do { } while(0) -#define DECREMENT_RECURSION_LEVEL(table_) do { } while(0) - -#endif /* defined(DEBUG) */ - -using namespace mozilla; +#endif PLDHashNumber PL_DHashStringKey(PLDHashTable* aTable, const void* aKey) @@ -232,10 +227,9 @@ PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, , mGeneration(0) , mEntryStore(nullptr) #ifdef DEBUG - , mRecursionLevel(0) + , mChecker() #endif { - METER(memset(&mStats, 0, sizeof(mStats))); } PLDHashTable& @@ -262,19 +256,17 @@ PLDHashTable::operator=(PLDHashTable&& aOther) mRemovedCount = Move(aOther.mRemovedCount); mGeneration = Move(aOther.mGeneration); mEntryStore = Move(aOther.mEntryStore); -#ifdef PL_DHASHMETER - mStats = Move(aOther.mStats); -#endif #ifdef DEBUG - // Atomic<> doesn't have an |operator=(Atomic<>&&)|. - mRecursionLevel = uint32_t(aOther.mRecursionLevel); + mChecker = Move(aOther.mChecker); #endif // Clear up |aOther| so its destruction will be a no-op. - aOther.mEntryStore = nullptr; + { #ifdef DEBUG - aOther.mRecursionLevel = 0; + AutoDestructorOp op(mChecker); #endif + aOther.mEntryStore = nullptr; + } return *this; } @@ -317,27 +309,25 @@ PLDHashTable::EntryIsFree(PLDHashEntryHdr* aEntry) PLDHashTable::~PLDHashTable() { +#ifdef DEBUG + AutoDestructorOp op(mChecker); +#endif + if (!mEntryStore) { return; } - INCREMENT_RECURSION_LEVEL(this); - /* Clear any remaining live entries. */ char* entryAddr = mEntryStore; char* entryLimit = entryAddr + Capacity() * mEntrySize; while (entryAddr < entryLimit) { PLDHashEntryHdr* entry = (PLDHashEntryHdr*)entryAddr; if (ENTRY_IS_LIVE(entry)) { - METER(mStats.mRemoveEnums++); mOps->clearEntry(this, entry); } entryAddr += mEntrySize; } - DECREMENT_RECURSION_LEVEL(this); - MOZ_ASSERT(RECURSION_LEVEL_SAFE_TO_FINISH(this)); - /* Free entry storage last. */ free(mEntryStore); mEntryStore = nullptr; @@ -370,7 +360,6 @@ PLDHashEntryHdr* PL_DHASH_FASTCALL PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) { MOZ_ASSERT(mEntryStore); - METER(mStats.mSearches++); NS_ASSERTION(!(aKeyHash & COLLISION_FLAG), "!(aKeyHash & COLLISION_FLAG)"); @@ -380,7 +369,6 @@ PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) /* Miss: return space for a new entry. */ if (EntryIsFree(entry)) { - METER(mStats.mMisses++); return (Reason == ForAdd) ? entry : nullptr; } @@ -388,7 +376,6 @@ PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) PLDHashMatchEntry matchEntry = mOps->matchEntry; if (MATCH_ENTRY_KEYHASH(entry, aKeyHash) && matchEntry(this, entry, aKey)) { - METER(mStats.mHits++); return entry; } @@ -414,20 +401,17 @@ PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) } } - METER(mStats.mSteps++); hash1 -= hash2; hash1 &= sizeMask; entry = ADDRESS_ENTRY(this, hash1); if (EntryIsFree(entry)) { - METER(mStats.mMisses++); return (Reason == ForAdd) ? (firstRemoved ? firstRemoved : entry) : nullptr; } if (MATCH_ENTRY_KEYHASH(entry, aKeyHash) && matchEntry(this, entry, aKey)) { - METER(mStats.mHits++); return entry; } } @@ -449,7 +433,6 @@ PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) PLDHashEntryHdr* PL_DHASH_FASTCALL PLDHashTable::FindFreeEntry(PLDHashNumber aKeyHash) { - METER(mStats.mSearches++); MOZ_ASSERT(mEntryStore); NS_ASSERTION(!(aKeyHash & COLLISION_FLAG), "!(aKeyHash & COLLISION_FLAG)"); @@ -460,7 +443,6 @@ PLDHashTable::FindFreeEntry(PLDHashNumber aKeyHash) /* Miss: return space for a new entry. */ if (EntryIsFree(entry)) { - METER(mStats.mMisses++); return entry; } @@ -474,13 +456,11 @@ PLDHashTable::FindFreeEntry(PLDHashNumber aKeyHash) "!ENTRY_IS_REMOVED(entry)"); entry->mKeyHash |= COLLISION_FLAG; - METER(mStats.mSteps++); hash1 -= hash2; hash1 &= sizeMask; entry = ADDRESS_ENTRY(this, hash1); if (EntryIsFree(entry)) { - METER(mStats.mMisses++); return entry; } } @@ -561,28 +541,23 @@ PLDHashTable::ComputeKeyHash(const void* aKey) MOZ_ALWAYS_INLINE PLDHashEntryHdr* PLDHashTable::Search(const void* aKey) { - INCREMENT_RECURSION_LEVEL(this); - - METER(mStats.mSearches++); +#ifdef DEBUG + AutoReadOp op(mChecker); +#endif PLDHashEntryHdr* entry = mEntryStore ? SearchTable(aKey, ComputeKeyHash(aKey)) : nullptr; - DECREMENT_RECURSION_LEVEL(this); - return entry; } MOZ_ALWAYS_INLINE PLDHashEntryHdr* PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) { - PLDHashNumber keyHash; - PLDHashEntryHdr* entry; - uint32_t capacity; - - MOZ_ASSERT(mRecursionLevel == 0); - INCREMENT_RECURSION_LEVEL(this); +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif // Allocate the entry storage if it hasn't already been allocated. if (!mEntryStore) { @@ -592,9 +567,7 @@ PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) &nbytes)); mEntryStore = (char*)malloc(nbytes); if (!mEntryStore) { - METER(mStats.mAddFailures++); - entry = nullptr; - goto exit; + return nullptr; } memset(mEntryStore, 0, nbytes); } @@ -604,15 +577,13 @@ PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) * in the table, we may grow once more than necessary, but only if we * are on the edge of being overloaded. */ - capacity = Capacity(); + uint32_t capacity = Capacity(); if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) { /* Compress if a quarter or more of all entries are removed. */ int deltaLog2; if (mRemovedCount >= capacity >> 2) { - METER(mStats.mCompresses++); deltaLog2 = 0; } else { - METER(mStats.mGrows++); deltaLog2 = 1; } @@ -623,9 +594,7 @@ PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) */ if (!ChangeTable(deltaLog2) && mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) { - METER(mStats.mAddFailures++); - entry = nullptr; - goto exit; + return nullptr; } } @@ -633,13 +602,11 @@ PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) * Look for entry after possibly growing, so we don't have to add it, * then skip it while growing the table and re-add it after. */ - keyHash = ComputeKeyHash(aKey); - entry = SearchTable(aKey, keyHash); + PLDHashNumber keyHash = ComputeKeyHash(aKey); + PLDHashEntryHdr* entry = SearchTable(aKey, keyHash); if (!ENTRY_IS_LIVE(entry)) { /* Initialize the entry, indicating that it's no longer free. */ - METER(mStats.mAddMisses++); if (ENTRY_IS_REMOVED(entry)) { - METER(mStats.mAddOverRemoved++); mRemovedCount--; keyHash |= COLLISION_FLAG; } @@ -649,12 +616,7 @@ PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) entry->mKeyHash = keyHash; mEntryCount++; } - METER(else { - mStats.mAddHits++; - }); -exit: - DECREMENT_RECURSION_LEVEL(this); return entry; } @@ -682,30 +644,24 @@ PLDHashTable::Add(const void* aKey) MOZ_ALWAYS_INLINE void PLDHashTable::Remove(const void* aKey) { - MOZ_ASSERT(mRecursionLevel == 0); - INCREMENT_RECURSION_LEVEL(this); +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif PLDHashEntryHdr* entry = mEntryStore ? SearchTable(aKey, ComputeKeyHash(aKey)) : nullptr; if (entry) { /* Clear this entry and mark it as "removed". */ - METER(mStats.mRemoveHits++); PL_DHashTableRawRemove(this, entry); /* Shrink if alpha is <= .25 and the table isn't too small already. */ uint32_t capacity = Capacity(); if (capacity > PL_DHASH_MIN_CAPACITY && mEntryCount <= MinLoad(capacity)) { - METER(mStats.mShrinks++); (void) ChangeTable(-1); } } - METER(else { - mStats.mRemoveMisses++; - }); - - DECREMENT_RECURSION_LEVEL(this); } PLDHashEntryHdr* PL_DHASH_FASTCALL @@ -736,9 +692,12 @@ PL_DHashTableRemove(PLDHashTable* aTable, const void* aKey) MOZ_ALWAYS_INLINE void PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) { - MOZ_ASSERT(mEntryStore); + // Unfortunately, we can only do weak checking here. That's because + // RawRemove() can be called legitimately while an Enumerate() call is + // active, which doesn't fit well into how Checker's mState variable works. + MOZ_ASSERT(mChecker.IsWritable()); - MOZ_ASSERT(mRecursionLevel != IMMUTABLE_RECURSION_LEVEL); + MOZ_ASSERT(mEntryStore); NS_ASSERTION(ENTRY_IS_LIVE(aEntry), "ENTRY_IS_LIVE(aEntry)"); @@ -749,7 +708,6 @@ PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) MARK_ENTRY_REMOVED(aEntry); mRemovedCount++; } else { - METER(mStats.mRemoveFrees++); MARK_ENTRY_FREE(aEntry); } mEntryCount--; @@ -770,7 +728,6 @@ PLDHashTable::ShrinkIfAppropriate() uint32_t capacity = Capacity(); if (mRemovedCount >= capacity >> 2 || (capacity > PL_DHASH_MIN_CAPACITY && mEntryCount <= MinLoad(capacity))) { - METER(mStats.mEnumShrinks++); uint32_t log2; BestCapacity(mEntryCount, &capacity, &log2); @@ -785,12 +742,19 @@ PLDHashTable::ShrinkIfAppropriate() MOZ_ALWAYS_INLINE uint32_t PLDHashTable::Enumerate(PLDHashEnumerator aEtor, void* aArg) { +#ifdef DEBUG + // Enumerate() can be a read operation or a write operation, depending on + // whether PL_DHASH_REMOVE is used. Assume for now it's a read; once the + // enumeration is done we'll know if that is false, and if so, + // retrospectively check it as a write. + bool wasIdleAndWritableAtStart = mChecker.IsIdle() && mChecker.IsWritable(); + AutoReadOp op(mChecker); +#endif + if (!mEntryStore) { return 0; } - INCREMENT_RECURSION_LEVEL(this); - char* entryAddr = mEntryStore; uint32_t capacity = Capacity(); uint32_t tableSize = capacity * mEntrySize; @@ -810,7 +774,6 @@ PLDHashTable::Enumerate(PLDHashEnumerator aEtor, void* aArg) if (ENTRY_IS_LIVE(entry)) { PLDHashOperator op = aEtor(this, entry, i++, aArg); if (op & PL_DHASH_REMOVE) { - METER(mStats.mRemoveEnums++); PL_DHashTableRawRemove(this, entry); didRemove = true; } @@ -824,7 +787,9 @@ PLDHashTable::Enumerate(PLDHashEnumerator aEtor, void* aArg) } } - MOZ_ASSERT(!didRemove || mRecursionLevel == 1); + // This is the retrospective check mentioned above. If we removed anything, + // then the table should have been idle and writable before we did so. + MOZ_ASSERT_IF(didRemove, wasIdleAndWritableAtStart); // Shrink the table if appropriate. Do this only if we removed above, so // non-removing enumerations can count on stable |mEntryStore| until the next @@ -833,8 +798,6 @@ PLDHashTable::Enumerate(PLDHashEnumerator aEtor, void* aArg) ShrinkIfAppropriate(); } - DECREMENT_RECURSION_LEVEL(this); - return i; } @@ -849,42 +812,26 @@ PL_DHashTableEnumerate(PLDHashTable* aTable, PLDHashEnumerator aEtor, return aTable->Enumerate(aEtor, aArg); } -struct SizeOfEntryExcludingThisArg -{ - size_t total; - PLDHashSizeOfEntryExcludingThisFun sizeOfEntryExcludingThis; - MallocSizeOf mallocSizeOf; - void* arg; // the arg passed by the user -}; - -static PLDHashOperator -SizeOfEntryExcludingThisEnumerator(PLDHashTable* aTable, PLDHashEntryHdr* aHdr, - uint32_t aNumber, void* aArg) -{ - SizeOfEntryExcludingThisArg* e = (SizeOfEntryExcludingThisArg*)aArg; - e->total += e->sizeOfEntryExcludingThis(aHdr, e->mallocSizeOf, e->arg); - return PL_DHASH_NEXT; -} - MOZ_ALWAYS_INLINE size_t PLDHashTable::SizeOfExcludingThis( PLDHashSizeOfEntryExcludingThisFun aSizeOfEntryExcludingThis, MallocSizeOf aMallocSizeOf, void* aArg /* = nullptr */) const { +#ifdef DEBUG + AutoReadOp op(mChecker); +#endif + if (!mEntryStore) { return 0; } - size_t n = 0; - n += aMallocSizeOf(mEntryStore); + size_t n = aMallocSizeOf(mEntryStore); if (aSizeOfEntryExcludingThis) { - SizeOfEntryExcludingThisArg arg2 = { - 0, aSizeOfEntryExcludingThis, aMallocSizeOf, aArg - }; - PL_DHashTableEnumerate(const_cast(this), - SizeOfEntryExcludingThisEnumerator, &arg2); - n += arg2.total; + for (auto iter = Iter(); !iter.Done(); iter.Next()) { + n += aSizeOfEntryExcludingThis(iter.Get(), aMallocSizeOf, aArg); + } } + return n; } @@ -922,7 +869,7 @@ PLDHashTable::Iterator::Iterator(Iterator&& aOther) , mCurrent(aOther.mCurrent) , mLimit(aOther.mLimit) { - // No need to change mRecursionLevel here. + // No need to change |mChecker| here. aOther.mTable = nullptr; aOther.mCurrent = nullptr; aOther.mLimit = nullptr; @@ -933,9 +880,9 @@ PLDHashTable::Iterator::Iterator(const PLDHashTable* aTable) , mCurrent(mTable->mEntryStore) , mLimit(mTable->mEntryStore + mTable->Capacity() * mTable->mEntrySize) { - // Make sure that modifications can't simultaneously happen while the - // iterator is active. - INCREMENT_RECURSION_LEVEL(mTable); +#ifdef DEBUG + mTable->mChecker.StartReadOp(); +#endif // Advance to the first live entry, or to the end if there are none. while (IsOnNonLiveEntry()) { @@ -945,9 +892,11 @@ PLDHashTable::Iterator::Iterator(const PLDHashTable* aTable) PLDHashTable::Iterator::~Iterator() { +#ifdef DEBUG if (mTable) { - DECREMENT_RECURSION_LEVEL(mTable); + mTable->mChecker.EndReadOp(); } +#endif } bool @@ -983,9 +932,12 @@ PLDHashTable::Iterator::Next() } PLDHashTable::RemovingIterator::RemovingIterator(RemovingIterator&& aOther) - : Iterator(mozilla::Move(aOther.mTable)) + : Iterator(mozilla::Move(aOther)) , mHaveRemoved(aOther.mHaveRemoved) { + // Do nothing with mChecker here. We treat RemovingIterator like Iterator -- + // i.e. as a read operation -- until the very end. Then, if any elements have + // been removed, we temporarily treat it as a write operation. } PLDHashTable::RemovingIterator::RemovingIterator(PLDHashTable* aTable) @@ -997,6 +949,10 @@ PLDHashTable::RemovingIterator::RemovingIterator(PLDHashTable* aTable) PLDHashTable::RemovingIterator::~RemovingIterator() { if (mHaveRemoved) { +#ifdef DEBUG + AutoIteratorRemovalOp op(mTable->mChecker); +#endif + // Why is this cast needed? In Iterator, |mTable| is const. In // RemovingIterator it should be non-const, but it inherits from Iterator // so that's not possible. But it's ok because RemovingIterator's @@ -1008,8 +964,6 @@ PLDHashTable::RemovingIterator::~RemovingIterator() void PLDHashTable::RemovingIterator::Remove() { - METER(mStats.mRemoveEnums++); - // This cast is needed for the same reason as the one in the destructor. const_cast(mTable)->RawRemove(Get()); mHaveRemoved = true; @@ -1019,7 +973,7 @@ PLDHashTable::RemovingIterator::Remove() MOZ_ALWAYS_INLINE void PLDHashTable::MarkImmutable() { - mRecursionLevel = IMMUTABLE_RECURSION_LEVEL; + mChecker.SetNonWritable(); } void @@ -1029,114 +983,3 @@ PL_DHashMarkTableImmutable(PLDHashTable* aTable) } #endif -#ifdef PL_DHASHMETER -#include - -void -PLDHashTable::DumpMeter(PLDHashEnumerator aDump, FILE* aFp) -{ - PLDHashNumber hash1, hash2, maxChainHash1, maxChainHash2; - double sqsum, mean, variance, sigma; - PLDHashEntryHdr* entry; - - char* entryAddr = mEntryStore; - int sizeLog2 = PL_DHASH_BITS - mHashShift; - uint32_t capacity = Capacity(); - uint32_t sizeMask = (1u << sizeLog2) - 1; - uint32_t chainCount = 0, maxChainLen = 0; - hash2 = 0; - sqsum = 0; - - MOZ_ASSERT_IF(capacity > 0, mEntryStore); - for (uint32_t i = 0; i < capacity; i++) { - entry = (PLDHashEntryHdr*)entryAddr; - entryAddr += mEntrySize; - if (!ENTRY_IS_LIVE(entry)) { - continue; - } - hash1 = HASH1(entry->mKeyHash & ~COLLISION_FLAG, mHashShift); - PLDHashNumber saveHash1 = hash1; - PLDHashEntryHdr* probe = ADDRESS_ENTRY(this, hash1); - uint32_t chainLen = 1; - if (probe == entry) { - /* Start of a (possibly unit-length) chain. */ - chainCount++; - } else { - hash2 = HASH2(entry->mKeyHash & ~COLLISION_FLAG, sizeLog2, mHashShift); - do { - chainLen++; - hash1 -= hash2; - hash1 &= sizeMask; - probe = ADDRESS_ENTRY(this, hash1); - } while (probe != entry); - } - sqsum += chainLen * chainLen; - if (chainLen > maxChainLen) { - maxChainLen = chainLen; - maxChainHash1 = saveHash1; - maxChainHash2 = hash2; - } - } - - if (mEntryCount && chainCount) { - mean = (double)mEntryCount / chainCount; - variance = chainCount * sqsum - mEntryCount * mEntryCount; - if (variance < 0 || chainCount == 1) { - variance = 0; - } else { - variance /= chainCount * (chainCount - 1); - } - sigma = sqrt(variance); - } else { - mean = sigma = 0; - } - - fprintf(aFp, "Double hashing statistics:\n"); - fprintf(aFp, " capacity (in entries): %u\n", Capacity()); - fprintf(aFp, " number of entries: %u\n", mEntryCount); - fprintf(aFp, " number of removed entries: %u\n", mRemovedCount); - fprintf(aFp, " number of searches: %u\n", mStats.mSearches); - fprintf(aFp, " number of hits: %u\n", mStats.mHits); - fprintf(aFp, " number of misses: %u\n", mStats.mMisses); - fprintf(aFp, " mean steps per search: %g\n", - mStats.mSearches ? (double)mStats.mSteps / mStats.mSearches : 0.); - fprintf(aFp, " mean hash chain length: %g\n", mean); - fprintf(aFp, " standard deviation: %g\n", sigma); - fprintf(aFp, " maximum hash chain length: %u\n", maxChainLen); - fprintf(aFp, " number of searches: %u\n", mStats.mSearches); - fprintf(aFp, " adds that made a new entry: %u\n", mStats.mAddMisses); - fprintf(aFp, "adds that recycled removeds: %u\n", mStats.mAddOverRemoved); - fprintf(aFp, " adds that found an entry: %u\n", mStats.mAddHits); - fprintf(aFp, " add failures: %u\n", mStats.mAddFailures); - fprintf(aFp, " useful removes: %u\n", mStats.mRemoveHits); - fprintf(aFp, " useless removes: %u\n", mStats.mRemoveMisses); - fprintf(aFp, "removes that freed an entry: %u\n", mStats.mRemoveFrees); - fprintf(aFp, " removes while enumerating: %u\n", mStats.mRemoveEnums); - fprintf(aFp, " number of grows: %u\n", mStats.mGrows); - fprintf(aFp, " number of shrinks: %u\n", mStats.mShrinks); - fprintf(aFp, " number of compresses: %u\n", mStats.mCompresses); - fprintf(aFp, "number of enumerate shrinks: %u\n", mStats.mEnumShrinks); - - if (aDump && maxChainLen && hash2) { - fputs("Maximum hash chain:\n", aFp); - hash1 = maxChainHash1; - hash2 = maxChainHash2; - entry = ADDRESS_ENTRY(this, hash1); - uint32_t i = 0; - do { - if (aDump(this, entry, i++, aFp) != PL_DHASH_NEXT) { - break; - } - hash1 -= hash2; - hash1 &= sizeMask; - entry = ADDRESS_ENTRY(this, hash1); - } while (!EntryIsFree(entry)); - } -} - -void -PL_DHashTableDumpMeter(PLDHashTable* aTable, PLDHashEnumerator aDump, FILE* aFp) -{ - aTable->DumpMeter(aDump, aFp); -} -#endif /* PL_DHASHMETER */ diff --git a/xpcom/glue/pldhash.h b/xpcom/glue/pldhash.h index 06a05975f2..14d634f467 100644 --- a/xpcom/glue/pldhash.h +++ b/xpcom/glue/pldhash.h @@ -17,10 +17,6 @@ #include "mozilla/Types.h" #include "nscore.h" -#ifdef PL_DHASHMETER -#include -#endif - #if defined(__GNUC__) && defined(__i386__) #define PL_DHASH_FASTCALL __attribute__ ((regparm (3),stdcall)) #elif defined(XP_WIN) @@ -144,6 +140,153 @@ typedef PLDHashOperator (*PLDHashEnumerator)(PLDHashTable* aTable, typedef size_t (*PLDHashSizeOfEntryExcludingThisFun)( PLDHashEntryHdr* aHdr, mozilla::MallocSizeOf aMallocSizeOf, void* aArg); +#ifdef DEBUG + +// This class does three kinds of checking: +// +// - that calls to one of |mOps| or to an enumerator do not cause re-entry into +// the table in an unsafe way; +// +// - that multiple threads do not access the table in an unsafe way; +// +// - that a table marked as immutable is not modified. +// +// "Safe" here means that multiple concurrent read operations are ok, but a +// write operation (i.e. one that can cause the entry storage to be reallocated +// or destroyed) cannot safely run concurrently with another read or write +// operation. This meaning of "safe" is only partial; for example, it does not +// cover whether a single entry in the table is modified by two separate +// threads. (Doing such checking would be much harder.) +// +// It does this with two variables: +// +// - mState, which embodies a tri-stage tagged union with the following +// variants: +// - Idle +// - Read(n), where 'n' is the number of concurrent read operations +// - Write +// +// - mIsWritable, which indicates if the table is mutable. +// +class Checker +{ +public: + MOZ_CONSTEXPR Checker() : mState(kIdle), mIsWritable(1) {} + + Checker& operator=(Checker&& aOther) { + // Atomic<> doesn't have an |operator=(Atomic<>&&)|. + mState = uint32_t(aOther.mState); + mIsWritable = uint32_t(aOther.mIsWritable); + + aOther.mState = kIdle; + + return *this; + } + + static bool IsIdle(uint32_t aState) { return aState == kIdle; } + static bool IsRead(uint32_t aState) { return kRead1 <= aState && + aState <= kReadMax; } + static bool IsRead1(uint32_t aState) { return aState == kRead1; } + static bool IsWrite(uint32_t aState) { return aState == kWrite; } + + bool IsIdle() const { return mState == kIdle; } + + bool IsWritable() const { return !!mIsWritable; } + + void SetNonWritable() { mIsWritable = 0; } + + // NOTE: the obvious way to implement these functions is to (a) check + // |mState| is reasonable, and then (b) update |mState|. But the lack of + // atomicity in such an implementation can cause problems if we get unlucky + // thread interleaving between (a) and (b). + // + // So instead for |mState| we are careful to (a) first get |mState|'s old + // value and assign it a new value in single atomic operation, and only then + // (b) check the old value was reasonable. This ensures we don't have + // interleaving problems. + // + // For |mIsWritable| we don't need to be as careful because it can only in + // transition in one direction (from writable to non-writable). + + void StartReadOp() + { + uint32_t oldState = mState++; // this is an atomic increment + MOZ_ASSERT(IsIdle(oldState) || IsRead(oldState)); + MOZ_ASSERT(oldState < kReadMax); // check for overflow + } + + void EndReadOp() + { + uint32_t oldState = mState--; // this is an atomic decrement + MOZ_ASSERT(IsRead(oldState)); + } + + void StartWriteOp() + { + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_ASSERT(IsIdle(oldState)); + } + + void EndWriteOp() + { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in StartWriteOp() + // occurred. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kIdle); + MOZ_ASSERT(IsWrite(oldState)); + } + + void StartIteratorRemovalOp() + { + // When doing removals at the end of iteration, we go from Read1 state to + // Write and then back. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_ASSERT(IsRead1(oldState)); + } + + void EndIteratorRemovalOp() + { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in + // StartIteratorRemovalOp() occurred. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kRead1); + MOZ_ASSERT(IsWrite(oldState)); + } + + void StartDestructorOp() + { + // A destructor op is like a write, but the table doesn't need to be + // writable. + uint32_t oldState = mState.exchange(kWrite); + MOZ_ASSERT(IsIdle(oldState)); + } + + void EndDestructorOp() + { + uint32_t oldState = mState.exchange(kIdle); + MOZ_ASSERT(IsWrite(oldState)); + } + +private: + // Things of note about the representation of |mState|. + // - The values between kRead1..kReadMax represent valid Read(n) values. + // - kIdle and kRead1 are deliberately chosen so that incrementing the - + // former gives the latter. + // - 9999 concurrent readers should be enough for anybody. + static const uint32_t kIdle = 0; + static const uint32_t kRead1 = 1; + static const uint32_t kReadMax = 9999; + static const uint32_t kWrite = 10000; + + mutable mozilla::Atomic mState; + mutable mozilla::Atomic mIsWritable; +}; +#endif + /* * A PLDHashTable may be allocated on the stack or within another structure or * class. No entry storage is allocated until the first element is added. This @@ -169,36 +312,9 @@ private: uint32_t mRemovedCount; /* removed entry sentinels in table */ uint32_t mGeneration; /* entry storage generation number */ char* mEntryStore; /* entry storage; allocated lazily */ -#ifdef PL_DHASHMETER - struct PLDHashStats - { - uint32_t mSearches; /* total number of table searches */ - uint32_t mSteps; /* hash chain links traversed */ - uint32_t mHits; /* searches that found key */ - uint32_t mMisses; /* searches that didn't find key */ - uint32_t mSearches; /* number of Search() calls */ - uint32_t mAddMisses; /* adds that miss, and do work */ - uint32_t mAddOverRemoved;/* adds that recycled a removed entry */ - uint32_t mAddHits; /* adds that hit an existing entry */ - uint32_t mAddFailures; /* out-of-memory during add growth */ - uint32_t mRemoveHits; /* removes that hit, and do work */ - uint32_t mRemoveMisses; /* useless removes that miss */ - uint32_t mRemoveFrees; /* removes that freed entry directly */ - uint32_t mRemoveEnums; /* removes done by Enumerate */ - uint32_t mGrows; /* table expansions */ - uint32_t mShrinks; /* table contractions */ - uint32_t mCompresses; /* table compressions */ - uint32_t mEnumShrinks; /* contractions after Enumerate */ - } mStats; -#endif #ifdef DEBUG - // We use an atomic counter here so that the various ++/-- operations can't - // get corrupted when a table is shared between threads. The associated - // assertions should in no way be taken to mean that thread safety is being - // validated! Proper synchronization and thread safety assertions must be - // employed by any consumers. - mutable mozilla::Atomic mRecursionLevel; + mutable Checker mChecker; #endif public: @@ -222,7 +338,7 @@ public: // to the destructor, which the move assignment operator does. , mEntryStore(nullptr) #ifdef DEBUG - , mRecursionLevel(0) + , mChecker() #endif { *this = mozilla::Move(aOther); @@ -288,10 +404,6 @@ public: void ClearEntryStub(PLDHashEntryHdr* aEntry); -#ifdef PL_DHASHMETER - void DumpMeter(PLDHashEnumerator aDump, FILE* aFp); -#endif - // This is an iterator for PLDHashtable. It is not safe to modify the // table while it is being iterated over; on debug builds, attempting to do // so will result in an assertion failure. @@ -560,8 +672,7 @@ PL_DHashTableRemove(PLDHashTable* aTable, const void* aKey); * NB: this is a "raw" or low-level routine, intended to be used only where * the inefficiency of a full PL_DHashTableRemove (which rehashes in order * to find the entry given its key) is not tolerable. This function does not - * shrink the table if it is underloaded. It does not update mStats #ifdef - * PL_DHASHMETER, either. + * shrink the table if it is underloaded. */ void PL_DHashTableRawRemove(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); @@ -606,9 +717,4 @@ size_t PL_DHashTableSizeOfIncludingThis( void PL_DHashMarkTableImmutable(PLDHashTable* aTable); #endif -#ifdef PL_DHASHMETER -void PL_DHashTableDumpMeter(PLDHashTable* aTable, - PLDHashEnumerator aDump, FILE* aFp); -#endif - #endif /* pldhash_h___ */ diff --git a/xpcom/tests/TestPLDHash.cpp b/xpcom/tests/gtest/TestPLDHash.cpp similarity index 70% rename from xpcom/tests/TestPLDHash.cpp rename to xpcom/tests/gtest/TestPLDHash.cpp index d86a2d5254..48edafff7d 100644 --- a/xpcom/tests/TestPLDHash.cpp +++ b/xpcom/tests/gtest/TestPLDHash.cpp @@ -4,15 +4,13 @@ * 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/. */ -#include #include "pldhash.h" +#include "gtest/gtest.h" // This test mostly focuses on edge cases. But more coverage of normal // operations wouldn't be a bad thing. -namespace TestPLDHash { - -static bool test_pldhash_Init_capacity_ok() +TEST(PLDHashTableTest, InitCapacityOk) { // Try the largest allowed capacity. With PL_DHASH_MAX_CAPACITY==1<<26, this // would allocate (if we added an element) 0.5GB of entry store on 32-bit @@ -29,11 +27,9 @@ static bool test_pldhash_Init_capacity_ok() // PLDHashTable t(PL_DHashGetStubOps(), sizeof(PLDHashEntryStub), PL_DHASH_MAX_INITIAL_LENGTH); - - return true; } -static bool test_pldhash_lazy_storage() +TEST(PLDHashTableTest, LazyStorage) { PLDHashTable t(PL_DHashGetStubOps(), sizeof(PLDHashEntryStub)); @@ -41,48 +37,28 @@ static bool test_pldhash_lazy_storage() // operations work appropriately when the table is empty and the storage // hasn't yet been allocated. - if (t.Capacity() != 0) { - return false; - } + ASSERT_EQ(t.Capacity(), 0u); + ASSERT_EQ(t.EntrySize(), sizeof(PLDHashEntryStub)); + ASSERT_EQ(t.EntryCount(), 0u); + ASSERT_EQ(t.Generation(), 0u); - if (t.EntrySize() != sizeof(PLDHashEntryStub)) { - return false; - } - - if (t.EntryCount() != 0) { - return false; - } - - if (t.Generation() != 0) { - return false; - } - - if (PL_DHashTableSearch(&t, (const void*)1)) { - return false; // search succeeded? - } + ASSERT_TRUE(!PL_DHashTableSearch(&t, (const void*)1)); // No result to check here, but call it to make sure it doesn't crash. PL_DHashTableRemove(&t, (const void*)2); - // Using a null |enumerator| should be fine because it shouldn't be called - // for an empty table. - PLDHashEnumerator enumerator = nullptr; - if (PL_DHashTableEnumerate(&t, enumerator, nullptr) != 0) { - return false; // enumeration count is non-zero? + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + ASSERT_TRUE(false); // shouldn't hit this on an empty table } - for (auto iter = t.Iter(); !iter.Done(); iter.Get()) { - return false; // shouldn't hit this on an empty table + for (auto iter = t.RemovingIter(); !iter.Done(); iter.Next()) { + ASSERT_TRUE(false); // shouldn't hit this on an empty table } // Using a null |mallocSizeOf| should be fine because it shouldn't be called // for an empty table. mozilla::MallocSizeOf mallocSizeOf = nullptr; - if (PL_DHashTableSizeOfExcludingThis(&t, nullptr, mallocSizeOf) != 0) { - return false; // size is non-zero? - } - - return true; + ASSERT_EQ(PL_DHashTableSizeOfExcludingThis(&t, nullptr, mallocSizeOf), 0u); } // A trivial hash function is good enough here. It's also super-fast for @@ -109,7 +85,7 @@ static const PLDHashTableOps trivialOps = { TrivialInitEntry }; -static bool test_pldhash_move_semantics() +TEST(PLDHashTableTest, MoveSemantics) { PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); PL_DHashTableAdd(&t1, (const void*)88); @@ -138,54 +114,38 @@ static bool test_pldhash_move_semantics() PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub)); PL_DHashTableAdd(&t9, (const void*)88); PLDHashTable t10(mozilla::Move(t9)); // new table constructed with inited - - return true; } -static bool test_pldhash_Clear() +TEST(PLDHashTableTest, Clear) { PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); t1.Clear(); - if (t1.EntryCount() != 0) { - return false; - } + ASSERT_EQ(t1.EntryCount(), 0u); t1.ClearAndPrepareForLength(100); - if (t1.EntryCount() != 0) { - return false; - } + ASSERT_EQ(t1.EntryCount(), 0u); PL_DHashTableAdd(&t1, (const void*)77); PL_DHashTableAdd(&t1, (const void*)88); PL_DHashTableAdd(&t1, (const void*)99); - if (t1.EntryCount() != 3) { - return false; - } + ASSERT_EQ(t1.EntryCount(), 3u); t1.Clear(); - if (t1.EntryCount() != 0) { - return false; - } + ASSERT_EQ(t1.EntryCount(), 0u); PL_DHashTableAdd(&t1, (const void*)55); PL_DHashTableAdd(&t1, (const void*)66); PL_DHashTableAdd(&t1, (const void*)77); PL_DHashTableAdd(&t1, (const void*)88); PL_DHashTableAdd(&t1, (const void*)99); - if (t1.EntryCount() != 5) { - return false; - } + ASSERT_EQ(t1.EntryCount(), 5u); t1.ClearAndPrepareForLength(8192); - if (t1.EntryCount() != 0) { - return false; - } - - return true; + ASSERT_EQ(t1.EntryCount(), 0u); } -static bool test_pldhash_Iterator() +TEST(PLDHashTableIterator, Iterator) { PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); @@ -200,7 +160,7 @@ static bool test_pldhash_Iterator() // Iterate through the empty table. for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { (void) iter.Get(); - return false; // shouldn't hit this + ASSERT_TRUE(false); // shouldn't hit this } // Add three entries. @@ -224,14 +184,10 @@ static bool test_pldhash_Iterator() } n++; } - if (!saw77 || !saw88 || !saw99 || n != 3) { - return false; - } - - return true; + ASSERT_TRUE(saw77 && saw88 && saw99 && n == 3); } -static bool test_pldhash_RemovingIterator() +TEST(PLDHashTableTest, RemovingIterator) { PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); @@ -239,8 +195,8 @@ static bool test_pldhash_RemovingIterator() // elision, compilers might optimize away move constructor calls for normal // iterator use. { - PLDHashTable::Iterator iter1(&t); - PLDHashTable::Iterator iter2(mozilla::Move(iter1)); + PLDHashTable::RemovingIterator iter1(&t); + PLDHashTable::RemovingIterator iter2(mozilla::Move(iter1)); } // First, we insert 64 items, which results in a capacity of 128, and a load @@ -248,18 +204,16 @@ static bool test_pldhash_RemovingIterator() for (intptr_t i = 0; i < 64; i++) { PL_DHashTableAdd(&t, (const void*)i); } - if (t.EntryCount() != 64 || t.Capacity() != 128) { - return false; - } + ASSERT_EQ(t.EntryCount(), 64u); + ASSERT_EQ(t.Capacity(), 128u); // The first removing iterator does no removing; capacity and entry count are // unchanged. for (PLDHashTable::RemovingIterator iter(&t); !iter.Done(); iter.Next()) { (void) iter.Get(); } - if (t.EntryCount() != 64 || t.Capacity() != 128) { - return false; - } + ASSERT_EQ(t.EntryCount(), 64u); + ASSERT_EQ(t.Capacity(), 128u); // The second removing iterator removes 16 items. This reduces the load // factor to 37.5% (48 / 128), which isn't low enough to shrink the table. @@ -269,9 +223,8 @@ static bool test_pldhash_RemovingIterator() iter.Remove(); } } - if (t.EntryCount() != 48 || t.Capacity() != 128) { - return false; - } + ASSERT_EQ(t.EntryCount(), 48u); + ASSERT_EQ(t.Capacity(), 128u); // The third removing iterator removes another 16 items. This reduces // the load factor to 25% (32 / 128), so the table is shrunk. @@ -281,25 +234,22 @@ static bool test_pldhash_RemovingIterator() iter.Remove(); } } - if (t.EntryCount() != 32 || t.Capacity() != 64) { - return false; - } + ASSERT_EQ(t.EntryCount(), 32u); + ASSERT_EQ(t.Capacity(), 64u); // The fourth removing iterator removes all remaining items. This reduces // the capacity to the minimum. for (auto iter = t.RemovingIter(); !iter.Done(); iter.Next()) { iter.Remove(); } - if (t.EntryCount() != 0 || t.Capacity() != PL_DHASH_MIN_CAPACITY) { - return false; - } - - return true; + ASSERT_EQ(t.EntryCount(), 0u); + ASSERT_EQ(t.Capacity(), unsigned(PL_DHASH_MIN_CAPACITY)); } -// See bug 931062, we skip this test on Android due to OOM. +// See bug 931062, we skip this test on Android due to OOM. Also, it's slow, +// and so should always be last. #ifndef MOZ_WIDGET_ANDROID -static bool test_pldhash_grow_to_max_capacity() +TEST(PLDHashTableTest, GrowToMaxCapacity) { // This is infallible. PLDHashTable* t = @@ -318,49 +268,10 @@ static bool test_pldhash_grow_to_max_capacity() // MaxLoadOnGrowthFailure()). if (numInserted != PL_DHASH_MAX_CAPACITY - (PL_DHASH_MAX_CAPACITY >> 5)) { delete t; - return false; + ASSERT_TRUE(false); } delete t; - return true; } #endif -//---- - -typedef bool (*TestFunc)(); -#define DECL_TEST(name) { #name, name } - -static const struct Test { - const char* name; - TestFunc func; -} tests[] = { - DECL_TEST(test_pldhash_Init_capacity_ok), - DECL_TEST(test_pldhash_lazy_storage), - DECL_TEST(test_pldhash_move_semantics), - DECL_TEST(test_pldhash_Clear), - DECL_TEST(test_pldhash_Iterator), - DECL_TEST(test_pldhash_RemovingIterator), -// See bug 931062, we skip this test on Android due to OOM. Also, it's slow, -// and so should always be last. -#ifndef MOZ_WIDGET_ANDROID - DECL_TEST(test_pldhash_grow_to_max_capacity), -#endif - { nullptr, nullptr } -}; - -} // namespace TestPLDHash - -using namespace TestPLDHash; - -int main(int argc, char *argv[]) -{ - bool success = true; - for (const Test* t = tests; t->name != nullptr; ++t) { - bool test_result = t->func(); - printf("%35s : %s\n", t->name, test_result ? "SUCCESS" : "FAILURE"); - if (!test_result) - success = false; - } - return success ? 0 : -1; -} diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build index 4a04a2be0c..b2343b494b 100644 --- a/xpcom/tests/gtest/moz.build +++ b/xpcom/tests/gtest/moz.build @@ -11,6 +11,7 @@ UNIFIED_SOURCES += [ 'TestEncoding.cpp', 'TestExpirationTracker.cpp', 'TestPipes.cpp', + 'TestPLDHash.cpp', 'TestPriorityQueue.cpp', 'TestSnappyStreams.cpp', 'TestStorageStream.cpp', diff --git a/xpcom/tests/moz.build b/xpcom/tests/moz.build index e5662cc971..71c61dc4a5 100644 --- a/xpcom/tests/moz.build +++ b/xpcom/tests/moz.build @@ -63,7 +63,6 @@ GeckoCppUnitTests([ 'TestObserverArray', 'TestObserverService', 'TestPipe', - 'TestPLDHash', 'TestStringAPI', 'TestTArray', 'TestTextFormatter',