summaryrefslogtreecommitdiffstats
path: root/layout/inspector
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /layout/inspector
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/inspector')
-rw-r--r--layout/inspector/InspectorFontFace.cpp281
-rw-r--r--layout/inspector/InspectorFontFace.h76
-rw-r--r--layout/inspector/InspectorUtils.cpp950
-rw-r--r--layout/inspector/InspectorUtils.h270
-rw-r--r--layout/inspector/ServoStyleRuleMap.cpp166
-rw-r--r--layout/inspector/ServoStyleRuleMap.h65
-rw-r--r--layout/inspector/inDeepTreeWalker.cpp334
-rw-r--r--layout/inspector/inDeepTreeWalker.h61
-rw-r--r--layout/inspector/inIDeepTreeWalker.idl43
-rw-r--r--layout/inspector/inLayoutUtils.cpp49
-rw-r--r--layout/inspector/inLayoutUtils.h28
-rw-r--r--layout/inspector/moz.build40
-rw-r--r--layout/inspector/tests/bug1202095-2.css7
-rw-r--r--layout/inspector/tests/bug1202095.css7
-rw-r--r--layout/inspector/tests/bug856317.css23
-rw-r--r--layout/inspector/tests/chrome/DejaVuSans.ttfbin0 -> 633604 bytes
-rw-r--r--layout/inspector/tests/chrome/GentiumPlus-R.woffbin0 -> 660480 bytes
-rw-r--r--layout/inspector/tests/chrome/chrome.toml43
-rw-r--r--layout/inspector/tests/chrome/test_bug467669.css8
-rw-r--r--layout/inspector/tests/chrome/test_bug467669.xhtml168
-rw-r--r--layout/inspector/tests/chrome/test_bug695639.css8
-rw-r--r--layout/inspector/tests/chrome/test_bug695639.xhtml74
-rw-r--r--layout/inspector/tests/chrome/test_bug708874.css33
-rw-r--r--layout/inspector/tests/chrome/test_bug708874.xhtml293
-rw-r--r--layout/inspector/tests/chrome/test_bug727834.css7
-rw-r--r--layout/inspector/tests/chrome/test_bug727834.xhtml86
-rw-r--r--layout/inspector/tests/chrome/test_fontFaceGeneric.xhtml62
-rw-r--r--layout/inspector/tests/chrome/test_fontFaceRanges.css17
-rw-r--r--layout/inspector/tests/chrome/test_fontFaceRanges.xhtml257
-rw-r--r--layout/inspector/tests/chrome/test_fontFeaturesAPI.css8
-rw-r--r--layout/inspector/tests/chrome/test_fontFeaturesAPI.xhtml238
-rw-r--r--layout/inspector/tests/chrome/test_fontVariationsAPI.css31
-rw-r--r--layout/inspector/tests/chrome/test_fontVariationsAPI.xhtml196
-rw-r--r--layout/inspector/tests/chrome/test_getBlockLineCounts.html80
-rw-r--r--layout/inspector/tests/chrome/test_getSelectorWarnings.html43
-rw-r--r--layout/inspector/tests/chrome/test_ua_rule_modification.html26
-rw-r--r--layout/inspector/tests/chrome/test_ua_sheet_disable.html20
-rw-r--r--layout/inspector/tests/chrome/test_visited_style.html77
-rw-r--r--layout/inspector/tests/file_bug522601.html17
-rw-r--r--layout/inspector/tests/file_getCSSStyleRules-alternate.html3
-rw-r--r--layout/inspector/tests/file_getCSSStyleRules-default.html3
-rw-r--r--layout/inspector/tests/getCSSStyleRules-1.css3
-rw-r--r--layout/inspector/tests/getCSSStyleRules-2.css3
-rw-r--r--layout/inspector/tests/mochitest.toml82
-rw-r--r--layout/inspector/tests/test_bug1006595.html84
-rw-r--r--layout/inspector/tests/test_bug462787.html99
-rw-r--r--layout/inspector/tests/test_bug462789.html76
-rw-r--r--layout/inspector/tests/test_bug522601-shadow.xhtml273
-rw-r--r--layout/inspector/tests/test_bug536379-2.html33
-rw-r--r--layout/inspector/tests/test_bug536379.html43
-rw-r--r--layout/inspector/tests/test_bug557726.html119
-rw-r--r--layout/inspector/tests/test_bug609549-shadow.xhtml79
-rw-r--r--layout/inspector/tests/test_bug856317.html83
-rw-r--r--layout/inspector/tests/test_bug877690.html279
-rw-r--r--layout/inspector/tests/test_color_to_rgba.html63
-rw-r--r--layout/inspector/tests/test_containing_block_of.html34
-rw-r--r--layout/inspector/tests/test_css_property_is_shorthand.html50
-rw-r--r--layout/inspector/tests/test_getCSSPseudoElementNames.html62
-rw-r--r--layout/inspector/tests/test_getCSSStyleRules.html204
-rw-r--r--layout/inspector/tests/test_getCSSStyleRules_pseudo.html76
-rw-r--r--layout/inspector/tests/test_getCSSStyleRules_slotted.html57
-rw-r--r--layout/inspector/tests/test_getRegisteredCssHighlights.html64
-rw-r--r--layout/inspector/tests/test_getRegisteredCustomProperties.html109
-rw-r--r--layout/inspector/tests/test_getRelativeRuleLine.html68
-rw-r--r--layout/inspector/tests/test_getRuleIndex.html88
-rw-r--r--layout/inspector/tests/test_get_all_style_sheets.html39
-rw-r--r--layout/inspector/tests/test_is_element_themed.html82
-rw-r--r--layout/inspector/tests/test_is_valid_css_color.html99
-rw-r--r--layout/inspector/tests/test_isinheritableproperty.html84
-rw-r--r--layout/inspector/tests/test_parseStyleSheet.html33
-rw-r--r--layout/inspector/tests/test_parseStyleSheetImport.html82
-rw-r--r--layout/inspector/tests/test_parseStyleSheet_nested.html79
-rw-r--r--layout/inspector/tests/test_rgba_to_color_name.html34
-rw-r--r--layout/inspector/tests/test_selectormatcheselement.html101
-rw-r--r--layout/inspector/tests/test_supports.html23
75 files changed, 6985 insertions, 0 deletions
diff --git a/layout/inspector/InspectorFontFace.cpp b/layout/inspector/InspectorFontFace.cpp
new file mode 100644
index 0000000000..59a336fd1b
--- /dev/null
+++ b/layout/inspector/InspectorFontFace.cpp
@@ -0,0 +1,281 @@
+/* -*- 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 "InspectorFontFace.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+#include "nsFontFaceLoader.h"
+#include "mozilla/gfx/2D.h"
+#include "brotli/decode.h"
+#include "zlib.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace dom {
+
+InspectorFontFace::InspectorFontFace(gfxFontEntry* aFontEntry,
+ gfxFontGroup* aFontGroup,
+ FontMatchType aMatchType)
+ : mFontEntry(aFontEntry), mFontGroup(aFontGroup), mMatchType(aMatchType) {
+ MOZ_COUNT_CTOR(InspectorFontFace);
+}
+
+InspectorFontFace::~InspectorFontFace() { MOZ_COUNT_DTOR(InspectorFontFace); }
+
+bool InspectorFontFace::FromFontGroup() {
+ return bool(mMatchType.kind & FontMatchType::Kind::kFontGroup);
+}
+
+bool InspectorFontFace::FromLanguagePrefs() {
+ return bool(mMatchType.kind & FontMatchType::Kind::kPrefsFallback);
+}
+
+bool InspectorFontFace::FromSystemFallback() {
+ return bool(mMatchType.kind & FontMatchType::Kind::kSystemFallback);
+}
+
+void InspectorFontFace::GetName(nsAString& aName) {
+ if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) {
+ NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData");
+ aName.Append(NS_ConvertUTF8toUTF16(mFontEntry->mUserFontData->mRealName));
+ } else {
+ aName.Append(NS_ConvertUTF8toUTF16(mFontEntry->RealFaceName()));
+ }
+}
+
+void InspectorFontFace::GetCSSFamilyName(nsAString& aCSSFamilyName) {
+ aCSSFamilyName.Append(NS_ConvertUTF8toUTF16(mFontEntry->FamilyName()));
+}
+
+void InspectorFontFace::GetCSSGeneric(nsAString& aName) {
+ if (mMatchType.generic != StyleGenericFontFamily::None) {
+ aName.AssignASCII(gfxPlatformFontList::GetGenericName(mMatchType.generic));
+ } else {
+ aName.Truncate(0);
+ }
+}
+
+CSSFontFaceRule* InspectorFontFace::GetRule() {
+ if (!mRule) {
+ // check whether this font entry is associated with an @font-face rule
+ // in the relevant font group's user font set
+ StyleLockedFontFaceRule* rule = nullptr;
+ if (mFontEntry->IsUserFont()) {
+ auto* fontFaceSet =
+ static_cast<FontFaceSetImpl*>(mFontGroup->GetUserFontSet());
+ if (fontFaceSet) {
+ rule = fontFaceSet->FindRuleForEntry(mFontEntry);
+ }
+ }
+ if (rule) {
+ // XXX It would be better if we can share this with CSSOM tree,
+ // but that may require us to create another map, which is not
+ // great either. As far as they would use the same backend, and
+ // we don't really support mutating @font-face rule via CSSOM,
+ // it's probably fine for now.
+ uint32_t line, column;
+ Servo_FontFaceRule_GetSourceLocation(rule, &line, &column);
+ mRule =
+ new CSSFontFaceRule(do_AddRef(rule), nullptr, nullptr, line, column);
+ }
+ }
+ return mRule;
+}
+
+int32_t InspectorFontFace::SrcIndex() {
+ if (mFontEntry->IsUserFont()) {
+ NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData");
+ return mFontEntry->mUserFontData->mSrcIndex;
+ }
+
+ return -1;
+}
+
+void InspectorFontFace::GetURI(nsAString& aURI) {
+ aURI.Truncate();
+ if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) {
+ NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData");
+ if (mFontEntry->mUserFontData->mURI) {
+ nsAutoCString spec;
+ mFontEntry->mUserFontData->mURI->GetSpec(spec);
+ AppendUTF8toUTF16(spec, aURI);
+ }
+ }
+}
+
+void InspectorFontFace::GetLocalName(nsAString& aLocalName) {
+ aLocalName.Truncate();
+ if (mFontEntry->IsLocalUserFont()) {
+ NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData");
+ aLocalName.Append(
+ NS_ConvertUTF8toUTF16(mFontEntry->mUserFontData->mLocalName));
+ }
+}
+
+void InspectorFontFace::GetFormat(nsAString& aFormat) {
+ aFormat.Truncate();
+ if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) {
+ NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData");
+ switch (mFontEntry->mUserFontData->mFormatHint) {
+ case StyleFontFaceSourceFormatKeyword::None:
+ break;
+ case StyleFontFaceSourceFormatKeyword::Collection:
+ aFormat.AssignLiteral("collection");
+ break;
+ case StyleFontFaceSourceFormatKeyword::Opentype:
+ aFormat.AssignLiteral("opentype");
+ break;
+ case StyleFontFaceSourceFormatKeyword::Truetype:
+ aFormat.AssignLiteral("truetype");
+ break;
+ case StyleFontFaceSourceFormatKeyword::EmbeddedOpentype:
+ aFormat.AssignLiteral("embedded-opentype");
+ break;
+ case StyleFontFaceSourceFormatKeyword::Svg:
+ aFormat.AssignLiteral("svg");
+ break;
+ case StyleFontFaceSourceFormatKeyword::Woff:
+ aFormat.AssignLiteral("woff");
+ break;
+ case StyleFontFaceSourceFormatKeyword::Woff2:
+ aFormat.AssignLiteral("woff2");
+ break;
+ case StyleFontFaceSourceFormatKeyword::Unknown:
+ aFormat.AssignLiteral("unknown!");
+ break;
+ }
+ }
+}
+
+void InspectorFontFace::GetMetadata(nsAString& aMetadata) {
+ aMetadata.Truncate();
+ if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) {
+ NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData");
+ const gfxUserFontData* userFontData = mFontEntry->mUserFontData.get();
+ if (userFontData->mMetadata.Length() && userFontData->mMetaOrigLen) {
+ nsAutoCString str;
+ str.SetLength(userFontData->mMetaOrigLen);
+ if (str.Length() == userFontData->mMetaOrigLen) {
+ switch (userFontData->mCompression) {
+ case gfxUserFontData::kZlibCompression: {
+ uLongf destLen = userFontData->mMetaOrigLen;
+ if (uncompress((Bytef*)(str.BeginWriting()), &destLen,
+ (const Bytef*)(userFontData->mMetadata.Elements()),
+ userFontData->mMetadata.Length()) == Z_OK &&
+ destLen == userFontData->mMetaOrigLen) {
+ AppendUTF8toUTF16(str, aMetadata);
+ }
+ } break;
+ case gfxUserFontData::kBrotliCompression: {
+ size_t decodedSize = userFontData->mMetaOrigLen;
+ if (BrotliDecoderDecompress(userFontData->mMetadata.Length(),
+ userFontData->mMetadata.Elements(),
+ &decodedSize,
+ (uint8_t*)str.BeginWriting()) == 1 &&
+ decodedSize == userFontData->mMetaOrigLen) {
+ AppendUTF8toUTF16(str, aMetadata);
+ }
+ } break;
+ }
+ }
+ }
+ }
+}
+
+// Append an OpenType tag to a string as a 4-ASCII-character code.
+static void AppendTagAsASCII(nsAString& aString, uint32_t aTag) {
+ aString.AppendPrintf("%c%c%c%c", (aTag >> 24) & 0xff, (aTag >> 16) & 0xff,
+ (aTag >> 8) & 0xff, aTag & 0xff);
+}
+
+void InspectorFontFace::GetVariationAxes(
+ nsTArray<InspectorVariationAxis>& aResult, ErrorResult& aRV) {
+ if (!mFontEntry->HasVariations()) {
+ return;
+ }
+ AutoTArray<gfxFontVariationAxis, 4> axes;
+ mFontEntry->GetVariationAxes(axes);
+ MOZ_ASSERT(!axes.IsEmpty());
+ if (!aResult.SetCapacity(axes.Length(), mozilla::fallible)) {
+ aRV.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ for (auto a : axes) {
+ InspectorVariationAxis& axis = *aResult.AppendElement();
+ AppendTagAsASCII(axis.mTag, a.mTag);
+ axis.mName.Append(NS_ConvertUTF8toUTF16(a.mName));
+ axis.mMinValue = a.mMinValue;
+ axis.mMaxValue = a.mMaxValue;
+ axis.mDefaultValue = a.mDefaultValue;
+ }
+}
+
+void InspectorFontFace::GetVariationInstances(
+ nsTArray<InspectorVariationInstance>& aResult, ErrorResult& aRV) {
+ if (!mFontEntry->HasVariations()) {
+ return;
+ }
+ AutoTArray<gfxFontVariationInstance, 16> instances;
+ mFontEntry->GetVariationInstances(instances);
+ if (!aResult.SetCapacity(instances.Length(), mozilla::fallible)) {
+ aRV.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ for (const auto& i : instances) {
+ InspectorVariationInstance& inst = *aResult.AppendElement();
+ inst.mName.Append(NS_ConvertUTF8toUTF16(i.mName));
+ // inst.mValues is a webidl sequence<>, which is a fallible array,
+ // so we are required to use fallible SetCapacity and AppendElement calls,
+ // and check the result. In practice we don't expect failure here; the
+ // list of values cannot get huge because of limits in the font format.
+ if (!inst.mValues.SetCapacity(i.mValues.Length(), mozilla::fallible)) {
+ aRV.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ for (const auto& v : i.mValues) {
+ InspectorVariationValue value;
+ AppendTagAsASCII(value.mAxis, v.mAxis);
+ value.mValue = v.mValue;
+ // This won't fail, because of SetCapacity above.
+ Unused << inst.mValues.AppendElement(value, mozilla::fallible);
+ }
+ }
+}
+
+void InspectorFontFace::GetFeatures(nsTArray<InspectorFontFeature>& aResult,
+ ErrorResult& aRV) {
+ AutoTArray<gfxFontFeatureInfo, 64> features;
+ mFontEntry->GetFeatureInfo(features);
+ if (features.IsEmpty()) {
+ return;
+ }
+ if (!aResult.SetCapacity(features.Length(), mozilla::fallible)) {
+ aRV.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ for (auto& f : features) {
+ InspectorFontFeature& feat = *aResult.AppendElement();
+ AppendTagAsASCII(feat.mTag, f.mTag);
+ AppendTagAsASCII(feat.mScript, f.mScript);
+ AppendTagAsASCII(feat.mLanguageSystem, f.mLangSys);
+ }
+}
+
+void InspectorFontFace::GetRanges(nsTArray<RefPtr<nsRange>>& aResult) {
+ aResult = mRanges.Clone();
+}
+
+void InspectorFontFace::AddRange(nsRange* aRange) {
+ mRanges.AppendElement(aRange);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/inspector/InspectorFontFace.h b/layout/inspector/InspectorFontFace.h
new file mode 100644
index 0000000000..2356ec7b9a
--- /dev/null
+++ b/layout/inspector/InspectorFontFace.h
@@ -0,0 +1,76 @@
+/* -*- 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_InspectorFontFace_h
+#define mozilla_InspectorFontFace_h
+
+#include "gfxTypes.h"
+#include "mozilla/dom/CSSFontFaceRule.h"
+#include "mozilla/dom/InspectorUtilsBinding.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsRange.h"
+
+class gfxFontEntry;
+class gfxFontGroup;
+
+namespace mozilla::dom {
+
+/**
+ * Information on font face usage by a given DOM Range, as returned by
+ * InspectorUtils.getUsedFontFaces.
+ */
+class InspectorFontFace final : public NonRefcountedDOMObject {
+ public:
+ InspectorFontFace(gfxFontEntry* aFontEntry, gfxFontGroup* aFontGroup,
+ FontMatchType aMatchType);
+
+ ~InspectorFontFace();
+
+ gfxFontEntry* GetFontEntry() const { return mFontEntry; }
+ void AddMatchType(FontMatchType aMatchType) { mMatchType |= aMatchType; }
+
+ void AddRange(nsRange* aRange);
+ size_t RangeCount() const { return mRanges.Length(); }
+
+ // Web IDL
+ bool FromFontGroup();
+ bool FromLanguagePrefs();
+ bool FromSystemFallback();
+ void GetName(nsAString& aName);
+ void GetCSSFamilyName(nsAString& aCSSFamilyName);
+ void GetCSSGeneric(nsAString& aGeneric);
+ CSSFontFaceRule* GetRule();
+ int32_t SrcIndex();
+ void GetURI(nsAString& aURI);
+ void GetLocalName(nsAString& aLocalName);
+ void GetFormat(nsAString& aFormat);
+ void GetMetadata(nsAString& aMetadata);
+
+ void GetVariationAxes(nsTArray<InspectorVariationAxis>& aResult,
+ ErrorResult& aRV);
+ void GetVariationInstances(nsTArray<InspectorVariationInstance>& aResult,
+ ErrorResult& aRV);
+ void GetFeatures(nsTArray<InspectorFontFeature>& aResult, ErrorResult& aRV);
+
+ void GetRanges(nsTArray<RefPtr<nsRange>>& aResult);
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return InspectorFontFace_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+ }
+
+ protected:
+ RefPtr<gfxFontEntry> mFontEntry;
+ RefPtr<gfxFontGroup> mFontGroup;
+ RefPtr<CSSFontFaceRule> mRule;
+ FontMatchType mMatchType;
+
+ nsTArray<RefPtr<nsRange>> mRanges;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_InspectorFontFace_h
diff --git a/layout/inspector/InspectorUtils.cpp b/layout/inspector/InspectorUtils.cpp
new file mode 100644
index 0000000000..e2111aa9bd
--- /dev/null
+++ b/layout/inspector/InspectorUtils.cpp
@@ -0,0 +1,950 @@
+/* -*- 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/ArrayUtils.h"
+
+#include "inLayoutUtils.h"
+
+#include "gfxTextRun.h"
+#include "mozilla/dom/HTMLSlotElement.h"
+#include "nsArray.h"
+#include "nsContentList.h"
+#include "nsString.h"
+#include "nsIContentInlines.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "ChildIterator.h"
+#include "nsComputedDOMStyle.h"
+#include "mozilla/EventStateManager.h"
+#include "nsAtom.h"
+#include "nsBlockFrame.h"
+#include "nsPresContext.h"
+#include "nsRange.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/CSSBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/CSSStyleRule.h"
+#include "mozilla/dom/CSSKeyframesRule.h"
+#include "mozilla/dom/Highlight.h"
+#include "mozilla/dom/HighlightRegistry.h"
+#include "mozilla/dom/InspectorUtilsBinding.h"
+#include "mozilla/dom/LinkStyle.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsCSSProps.h"
+#include "nsCSSValue.h"
+#include "nsColor.h"
+#include "mozilla/ServoStyleSet.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsStyleUtil.h"
+#include "nsQueryObject.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleRuleMap.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/dom/InspectorUtils.h"
+#include "mozilla/dom/InspectorFontFace.h"
+#include "mozilla/gfx/Matrix.h"
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace dom {
+
+static already_AddRefed<const ComputedStyle> GetCleanComputedStyleForElement(
+ dom::Element* aElement, PseudoStyleType aPseudo,
+ nsAtom* aFunctionalPseudoParameter) {
+ MOZ_ASSERT(aElement);
+
+ Document* doc = aElement->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (!presContext) {
+ return nullptr;
+ }
+
+ presContext->EnsureSafeToHandOutCSSRules();
+
+ return nsComputedDOMStyle::GetComputedStyle(aElement, aPseudo,
+ aFunctionalPseudoParameter);
+}
+
+/* static */
+void InspectorUtils::GetAllStyleSheets(GlobalObject& aGlobalObject,
+ Document& aDocument, bool aDocumentOnly,
+ nsTArray<RefPtr<StyleSheet>>& aResult) {
+ // Get the agent, then user and finally xbl sheets in the style set.
+ PresShell* presShell = aDocument.GetPresShell();
+ nsTHashSet<StyleSheet*> sheetSet;
+
+ if (presShell) {
+ ServoStyleSet* styleSet = presShell->StyleSet();
+
+ if (!aDocumentOnly) {
+ const StyleOrigin kOrigins[] = {StyleOrigin::UserAgent,
+ StyleOrigin::User};
+ for (const auto origin : kOrigins) {
+ for (size_t i = 0, count = styleSet->SheetCount(origin); i < count;
+ i++) {
+ aResult.AppendElement(styleSet->SheetAt(origin, i));
+ }
+ }
+ }
+
+ AutoTArray<StyleSheet*, 32> nonDocumentSheets;
+ styleSet->AppendAllNonDocumentAuthorSheets(nonDocumentSheets);
+
+ // The non-document stylesheet array can have duplicates due to adopted
+ // stylesheets.
+ nsTHashSet<StyleSheet*> sheetSet;
+ for (StyleSheet* sheet : nonDocumentSheets) {
+ if (sheetSet.EnsureInserted(sheet)) {
+ aResult.AppendElement(sheet);
+ }
+ }
+ }
+
+ // Get the document sheets.
+ for (size_t i = 0; i < aDocument.SheetCount(); i++) {
+ aResult.AppendElement(aDocument.SheetAt(i));
+ }
+
+ for (auto& sheet : aDocument.AdoptedStyleSheets()) {
+ if (sheetSet.EnsureInserted(sheet)) {
+ aResult.AppendElement(sheet);
+ }
+ }
+}
+
+bool InspectorUtils::IsIgnorableWhitespace(CharacterData& aDataNode) {
+ if (!aDataNode.TextIsOnlyWhitespace()) {
+ return false;
+ }
+
+ // Okay. We have only white space. Let's check the white-space
+ // property now and make sure that this isn't preformatted text...
+ if (nsIFrame* frame = aDataNode.GetPrimaryFrame()) {
+ return !frame->StyleText()->WhiteSpaceIsSignificant();
+ }
+
+ // empty inter-tag text node without frame, e.g., in between <table>\n<tr>
+ return true;
+}
+
+/* static */
+nsINode* InspectorUtils::GetParentForNode(nsINode& aNode,
+ bool aShowingAnonymousContent) {
+ if (nsINode* parent = aNode.GetParentNode()) {
+ return parent;
+ }
+ if (aNode.IsDocument()) {
+ return inLayoutUtils::GetContainerFor(*aNode.AsDocument());
+ }
+ if (aShowingAnonymousContent) {
+ if (auto* frag = DocumentFragment::FromNode(aNode)) {
+ // This deals with shadow roots and HTMLTemplateElement.content.
+ return frag->GetHost();
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+void InspectorUtils::GetChildrenForNode(nsINode& aNode,
+ bool aShowingAnonymousContent,
+ bool aIncludeAssignedNodes,
+ bool aIncludeSubdocuments,
+ nsTArray<RefPtr<nsINode>>& aResult) {
+ if (aIncludeSubdocuments) {
+ if (auto* doc = inLayoutUtils::GetSubDocumentFor(&aNode)) {
+ aResult.AppendElement(doc);
+ // XXX Do we really want to early-return?
+ return;
+ }
+ }
+
+ if (!aShowingAnonymousContent || !aNode.IsContent()) {
+ for (nsINode* child = aNode.GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ aResult.AppendElement(child);
+ }
+ return;
+ }
+
+ if (auto* tmpl = HTMLTemplateElement::FromNode(aNode)) {
+ aResult.AppendElement(tmpl->Content());
+ // XXX Do we really want to early-return?
+ return;
+ }
+
+ if (auto* element = Element::FromNode(aNode)) {
+ if (auto* shadow = element->GetShadowRoot()) {
+ aResult.AppendElement(shadow);
+ }
+ }
+ nsIContent* parent = aNode.AsContent();
+ if (auto* node = nsLayoutUtils::GetMarkerPseudo(parent)) {
+ aResult.AppendElement(node);
+ }
+ if (auto* node = nsLayoutUtils::GetBeforePseudo(parent)) {
+ aResult.AppendElement(node);
+ }
+ if (aIncludeAssignedNodes) {
+ if (auto* slot = HTMLSlotElement::FromNode(aNode)) {
+ for (nsINode* node : slot->AssignedNodes()) {
+ aResult.AppendElement(node);
+ }
+ }
+ }
+ for (nsIContent* node = parent->GetFirstChild(); node;
+ node = node->GetNextSibling()) {
+ aResult.AppendElement(node);
+ }
+ AutoTArray<nsIContent*, 4> anonKids;
+ nsContentUtils::AppendNativeAnonymousChildren(parent, anonKids,
+ nsIContent::eAllChildren);
+ for (nsIContent* node : anonKids) {
+ aResult.AppendElement(node);
+ }
+ if (auto* node = nsLayoutUtils::GetAfterPseudo(parent)) {
+ aResult.AppendElement(node);
+ }
+}
+
+/* static */
+void InspectorUtils::GetCSSStyleRules(GlobalObject& aGlobalObject,
+ Element& aElement,
+ const nsAString& aPseudo,
+ bool aIncludeVisitedStyle,
+ nsTArray<RefPtr<CSSStyleRule>>& aResult) {
+ auto [type, functionalPseudoParameter] =
+ nsCSSPseudoElements::ParsePseudoElement(aPseudo,
+ CSSEnabledState::ForAllContent);
+ if (!type) {
+ return;
+ }
+
+ RefPtr<const ComputedStyle> computedStyle = GetCleanComputedStyleForElement(
+ &aElement, *type, functionalPseudoParameter);
+ if (!computedStyle) {
+ // This can fail for elements that are not in the document or
+ // if the document they're in doesn't have a presshell. Bail out.
+ return;
+ }
+
+ if (aIncludeVisitedStyle) {
+ if (auto* styleIfVisited = computedStyle->GetStyleIfVisited()) {
+ computedStyle = styleIfVisited;
+ }
+ }
+
+ Document* doc = aElement.OwnerDoc();
+ PresShell* presShell = doc->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ AutoTArray<const StyleLockedStyleRule*, 8> rawRuleList;
+ Servo_ComputedValues_GetStyleRuleList(computedStyle, &rawRuleList);
+
+ AutoTArray<ServoStyleRuleMap*, 8> maps;
+ {
+ ServoStyleSet* styleSet = presShell->StyleSet();
+ ServoStyleRuleMap* map = styleSet->StyleRuleMap();
+ maps.AppendElement(map);
+ }
+
+ // Now shadow DOM stuff...
+ if (auto* shadow = aElement.GetShadowRoot()) {
+ maps.AppendElement(&shadow->ServoStyleRuleMap());
+ }
+
+ // Now NAC:
+ for (auto* el = aElement.GetClosestNativeAnonymousSubtreeRootParentOrHost();
+ el; el = el->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
+ if (auto* shadow = el->GetShadowRoot()) {
+ maps.AppendElement(&shadow->ServoStyleRuleMap());
+ }
+ }
+
+ for (auto* shadow = aElement.GetContainingShadow(); shadow;
+ shadow = shadow->Host()->GetContainingShadow()) {
+ maps.AppendElement(&shadow->ServoStyleRuleMap());
+ }
+
+ // Rules from the assigned slot.
+ for (auto* slot = aElement.GetAssignedSlot(); slot;
+ slot = slot->GetAssignedSlot()) {
+ if (auto* shadow = slot->GetContainingShadow()) {
+ maps.AppendElement(&shadow->ServoStyleRuleMap());
+ }
+ }
+
+ // Find matching rules in the table.
+ for (const StyleLockedStyleRule* rawRule : Reversed(rawRuleList)) {
+ CSSStyleRule* rule = nullptr;
+ for (ServoStyleRuleMap* map : maps) {
+ rule = map->Lookup(rawRule);
+ if (rule) {
+ break;
+ }
+ }
+ if (rule) {
+ aResult.AppendElement(rule);
+ } else {
+#ifdef DEBUG
+ aElement.Dump();
+ printf_stderr("\n\n----\n\n");
+ computedStyle->DumpMatchedRules();
+ nsAutoCString str;
+ Servo_StyleRule_Debug(rawRule, &str);
+ printf_stderr("\n\n----\n\n");
+ printf_stderr("%s\n", str.get());
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "We should be able to map raw rule %p to a rule in one of the %zu "
+ "maps: %s\n",
+ rawRule, maps.Length(), str.get());
+#endif
+ }
+ }
+}
+
+/* static */
+uint32_t InspectorUtils::GetRuleLine(GlobalObject& aGlobal, css::Rule& aRule) {
+ uint32_t line = aRule.GetLineNumber();
+ if (StyleSheet* sheet = aRule.GetStyleSheet()) {
+ if (auto* link = LinkStyle::FromNodeOrNull(sheet->GetOwnerNode())) {
+ line += link->GetLineNumber();
+ }
+ }
+ return line;
+}
+
+/* static */
+uint32_t InspectorUtils::GetRuleColumn(GlobalObject& aGlobal,
+ css::Rule& aRule) {
+ return aRule.GetColumnNumber();
+}
+
+/* static */
+uint32_t InspectorUtils::GetRelativeRuleLine(GlobalObject& aGlobal,
+ css::Rule& aRule) {
+ // Rule lines are 0-based, but inspector wants 1-based.
+ return aRule.GetLineNumber() + 1;
+}
+
+
+void InspectorUtils::GetRuleIndex(GlobalObject& aGlobal,
+ css::Rule& aRule,
+ nsTArray<uint32_t>& aResult) {
+ css::Rule* currentRule = &aRule;
+
+ do {
+ css::Rule* parentRule = currentRule->GetParentRule();
+ dom::CSSRuleList* ruleList = nullptr;
+
+ if (parentRule) {
+ if (parentRule->IsGroupRule()) {
+ ruleList = static_cast<css::GroupRule*>(parentRule)->CssRules();
+ } else if (parentRule->Type() == StyleCssRuleType::Keyframes) {
+ ruleList = static_cast<CSSKeyframesRule*>(parentRule)->CssRules();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unknown parent rule type?");
+ }
+ } else if (StyleSheet* sheet = currentRule->GetStyleSheet()) {
+ ruleList = sheet->GetCssRulesInternal();
+ }
+
+ if (!ruleList) {
+ return;
+ }
+
+ bool found = false;
+ for (uint32_t i = 0, len = ruleList->Length(); i < len; ++i) {
+ css::Rule* rule = ruleList->Item(i);
+ if (currentRule == rule) {
+ found = true;
+ aResult.InsertElementAt(0, i);
+ break;
+ }
+ }
+
+ if (!found) {
+ return;
+ }
+
+ currentRule = parentRule;
+ } while (currentRule);
+}
+
+/* static */
+bool InspectorUtils::HasRulesModifiedByCSSOM(GlobalObject& aGlobal,
+ StyleSheet& aSheet) {
+ return aSheet.HasModifiedRulesForDevtools();
+}
+
+static void CollectRules(ServoCSSRuleList& aRuleList,
+ nsTArray<RefPtr<css::Rule>>& aResult) {
+ for (uint32_t i = 0, len = aRuleList.Length(); i < len; ++i) {
+ css::Rule* rule = aRuleList.GetRule(i);
+ aResult.AppendElement(rule);
+ if (rule->IsGroupRule()) {
+ CollectRules(*static_cast<css::GroupRule*>(rule)->CssRules(), aResult);
+ }
+ }
+}
+
+void InspectorUtils::GetAllStyleSheetCSSStyleRules(
+ GlobalObject& aGlobal, StyleSheet& aSheet,
+ nsTArray<RefPtr<css::Rule>>& aResult) {
+ CollectRules(*aSheet.GetCssRulesInternal(), aResult);
+}
+
+/* static */
+bool InspectorUtils::IsInheritedProperty(GlobalObject& aGlobalObject,
+ Document& aDocument,
+ const nsACString& aPropertyName) {
+ return Servo_Property_IsInherited(aDocument.EnsureStyleSet().RawData(),
+ &aPropertyName);
+}
+
+/* static */
+void InspectorUtils::GetCSSPropertyNames(GlobalObject& aGlobalObject,
+ const PropertyNamesOptions& aOptions,
+ nsTArray<nsString>& aResult) {
+ CSSEnabledState enabledState = aOptions.mIncludeExperimentals
+ ? CSSEnabledState::IgnoreEnabledState
+ : CSSEnabledState::ForAllContent;
+
+ auto appendProperty = [enabledState, &aResult](uint32_t prop) {
+ nsCSSPropertyID cssProp = nsCSSPropertyID(prop);
+ if (nsCSSProps::IsEnabled(cssProp, enabledState)) {
+ aResult.AppendElement(
+ NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(cssProp)));
+ }
+ };
+
+ uint32_t prop = 0;
+ for (; prop < eCSSProperty_COUNT_no_shorthands; ++prop) {
+ if (!nsCSSProps::PropHasFlags(nsCSSPropertyID(prop),
+ CSSPropFlags::Inaccessible)) {
+ appendProperty(prop);
+ }
+ }
+
+ if (aOptions.mIncludeShorthands) {
+ for (; prop < eCSSProperty_COUNT; ++prop) {
+ appendProperty(prop);
+ }
+ }
+
+ if (aOptions.mIncludeAliases) {
+ for (prop = eCSSProperty_COUNT; prop < eCSSProperty_COUNT_with_aliases;
+ ++prop) {
+ appendProperty(prop);
+ }
+ }
+}
+
+/* static */
+void InspectorUtils::GetCSSPropertyPrefs(GlobalObject& aGlobalObject,
+ nsTArray<PropertyPref>& aResult) {
+ for (const auto* src = nsCSSProps::kPropertyPrefTable;
+ src->mPropID != eCSSProperty_UNKNOWN; src++) {
+ PropertyPref& dest = *aResult.AppendElement();
+ dest.mName.Assign(
+ NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(src->mPropID)));
+ dest.mPref.AssignASCII(src->mPref);
+ }
+}
+
+/* static */
+void InspectorUtils::GetSubpropertiesForCSSProperty(GlobalObject& aGlobal,
+ const nsACString& aProperty,
+ nsTArray<nsString>& aResult,
+ ErrorResult& aRv) {
+ nsCSSPropertyID propertyID = nsCSSProps::LookupProperty(aProperty);
+
+ if (propertyID == eCSSProperty_UNKNOWN) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (propertyID == eCSSPropertyExtra_variable) {
+ aResult.AppendElement(NS_ConvertUTF8toUTF16(aProperty));
+ return;
+ }
+
+ if (!nsCSSProps::IsShorthand(propertyID)) {
+ nsString* name = aResult.AppendElement();
+ CopyASCIItoUTF16(nsCSSProps::GetStringValue(propertyID), *name);
+ return;
+ }
+
+ for (const nsCSSPropertyID* props =
+ nsCSSProps::SubpropertyEntryFor(propertyID);
+ *props != eCSSProperty_UNKNOWN; ++props) {
+ nsString* name = aResult.AppendElement();
+ CopyASCIItoUTF16(nsCSSProps::GetStringValue(*props), *name);
+ }
+}
+
+/* static */
+bool InspectorUtils::CssPropertyIsShorthand(GlobalObject& aGlobalObject,
+ const nsACString& aProperty,
+ ErrorResult& aRv) {
+ bool found;
+ bool isShorthand = Servo_Property_IsShorthand(&aProperty, &found);
+ if (!found) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+ return isShorthand;
+}
+
+// This should match the constants in specified_value_info.rs
+//
+// Once we can use bitflags in consts, we can also cbindgen that and use them
+// here instead.
+static uint8_t ToServoCssType(InspectorPropertyType aType) {
+ switch (aType) {
+ case InspectorPropertyType::Color:
+ return 1;
+ case InspectorPropertyType::Gradient:
+ return 1 << 1;
+ case InspectorPropertyType::Timing_function:
+ return 1 << 2;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown property type?");
+ return 0;
+ }
+}
+
+bool InspectorUtils::Supports(GlobalObject&, const nsACString& aDeclaration,
+ const SupportsOptions& aOptions) {
+ return Servo_CSSSupports(&aDeclaration, aOptions.mUserAgent, aOptions.mChrome,
+ aOptions.mQuirks);
+}
+
+bool InspectorUtils::CssPropertySupportsType(GlobalObject& aGlobalObject,
+ const nsACString& aProperty,
+ InspectorPropertyType aType,
+ ErrorResult& aRv) {
+ bool found;
+ bool result =
+ Servo_Property_SupportsType(&aProperty, ToServoCssType(aType), &found);
+ if (!found) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+ return result;
+}
+
+/* static */
+void InspectorUtils::GetCSSValuesForProperty(GlobalObject& aGlobalObject,
+ const nsACString& aProperty,
+ nsTArray<nsString>& aResult,
+ ErrorResult& aRv) {
+ bool found;
+ Servo_Property_GetCSSValuesForProperty(&aProperty, &found, &aResult);
+ if (!found) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+/* static */
+void InspectorUtils::RgbToColorName(GlobalObject&, uint8_t aR, uint8_t aG,
+ uint8_t aB, nsACString& aColorName) {
+ Servo_SlowRgbToColorName(aR, aG, aB, &aColorName);
+}
+
+/* static */
+void InspectorUtils::ColorToRGBA(GlobalObject&, const nsACString& aColorString,
+ const Document* aDoc,
+ Nullable<InspectorRGBATuple>& aResult) {
+ nscolor color = NS_RGB(0, 0, 0);
+
+ ServoStyleSet* styleSet = nullptr;
+ if (aDoc) {
+ if (PresShell* ps = aDoc->GetPresShell()) {
+ styleSet = ps->StyleSet();
+ }
+ }
+
+ if (!ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), aColorString,
+ &color)) {
+ aResult.SetNull();
+ return;
+ }
+
+ InspectorRGBATuple& tuple = aResult.SetValue();
+ tuple.mR = NS_GET_R(color);
+ tuple.mG = NS_GET_G(color);
+ tuple.mB = NS_GET_B(color);
+ tuple.mA = nsStyleUtil::ColorComponentToFloat(NS_GET_A(color));
+}
+
+/* static */
+bool InspectorUtils::IsValidCSSColor(GlobalObject& aGlobalObject,
+ const nsACString& aColorString) {
+ return ServoCSSParser::IsValidCSSColor(aColorString);
+}
+
+/* static */
+bool InspectorUtils::SetContentState(GlobalObject& aGlobalObject,
+ Element& aElement, uint64_t aState,
+ ErrorResult& aRv) {
+ RefPtr<EventStateManager> esm =
+ inLayoutUtils::GetEventStateManagerFor(aElement);
+ ElementState state(aState);
+ if (!esm || !EventStateManager::ManagesState(state)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+ return esm->SetContentState(&aElement, state);
+}
+
+/* static */
+bool InspectorUtils::RemoveContentState(GlobalObject& aGlobalObject,
+ Element& aElement, uint64_t aState,
+ bool aClearActiveDocument,
+ ErrorResult& aRv) {
+ RefPtr<EventStateManager> esm =
+ inLayoutUtils::GetEventStateManagerFor(aElement);
+ ElementState state(aState);
+ if (!esm || !EventStateManager::ManagesState(state)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ bool result = esm->SetContentState(nullptr, state);
+
+ if (aClearActiveDocument && state == ElementState::ACTIVE) {
+ EventStateManager* activeESM = static_cast<EventStateManager*>(
+ EventStateManager::GetActiveEventStateManager());
+ if (activeESM == esm) {
+ EventStateManager::ClearGlobalActiveContent(nullptr);
+ }
+ }
+
+ return result;
+}
+
+/* static */
+uint64_t InspectorUtils::GetContentState(GlobalObject& aGlobalObject,
+ Element& aElement) {
+ // NOTE: if this method is removed,
+ // please remove GetInternalValue from ElementState
+ return aElement.State().GetInternalValue();
+}
+
+/* static */
+void InspectorUtils::GetUsedFontFaces(GlobalObject& aGlobalObject,
+ nsRange& aRange, uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace,
+ nsLayoutUtils::UsedFontFaceList& aResult,
+ ErrorResult& aRv) {
+ nsresult rv =
+ aRange.GetUsedFontFaces(aResult, aMaxRanges, aSkipCollapsedWhitespace);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+static ElementState GetStatesForPseudoClass(const nsAString& aStatePseudo) {
+ if (aStatePseudo.IsEmpty() || aStatePseudo[0] != u':') {
+ return ElementState();
+ }
+ NS_ConvertUTF16toUTF8 statePseudo(Substring(aStatePseudo, 1));
+ return ElementState(Servo_PseudoClass_GetStates(&statePseudo));
+}
+
+/* static */
+void InspectorUtils::GetCSSPseudoElementNames(GlobalObject& aGlobalObject,
+ nsTArray<nsString>& aResult) {
+ const auto kPseudoCount =
+ static_cast<size_t>(PseudoStyleType::CSSPseudoElementsEnd);
+ for (size_t i = 0; i < kPseudoCount; ++i) {
+ PseudoStyleType type = static_cast<PseudoStyleType>(i);
+ if (!nsCSSPseudoElements::IsEnabled(type, CSSEnabledState::ForAllContent)) {
+ continue;
+ }
+ auto& string = *aResult.AppendElement();
+ // Use two semi-colons (though internally we use one).
+ string.Append(u':');
+ nsAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type);
+ string.Append(nsDependentAtomString(atom));
+ }
+}
+
+/* static */
+void InspectorUtils::AddPseudoClassLock(GlobalObject& aGlobalObject,
+ Element& aElement,
+ const nsAString& aPseudoClass,
+ bool aEnabled) {
+ ElementState state = GetStatesForPseudoClass(aPseudoClass);
+ if (state.IsEmpty()) {
+ return;
+ }
+
+ aElement.LockStyleStates(state, aEnabled);
+}
+
+/* static */
+void InspectorUtils::RemovePseudoClassLock(GlobalObject& aGlobal,
+ Element& aElement,
+ const nsAString& aPseudoClass) {
+ ElementState state = GetStatesForPseudoClass(aPseudoClass);
+ if (state.IsEmpty()) {
+ return;
+ }
+
+ aElement.UnlockStyleStates(state);
+}
+
+/* static */
+bool InspectorUtils::HasPseudoClassLock(GlobalObject& aGlobalObject,
+ Element& aElement,
+ const nsAString& aPseudoClass) {
+ ElementState state = GetStatesForPseudoClass(aPseudoClass);
+ if (state.IsEmpty()) {
+ return false;
+ }
+
+ ElementState locks = aElement.LockedStyleStates().mLocks;
+ return locks.HasAllStates(state);
+}
+
+/* static */
+void InspectorUtils::ClearPseudoClassLocks(GlobalObject& aGlobalObject,
+ Element& aElement) {
+ aElement.ClearStyleStateLocks();
+}
+
+/* static */
+void InspectorUtils::ParseStyleSheet(GlobalObject& aGlobalObject,
+ StyleSheet& aSheet,
+ const nsACString& aInput,
+ ErrorResult& aRv) {
+ aSheet.ReparseSheet(aInput, aRv);
+}
+
+bool InspectorUtils::IsCustomElementName(GlobalObject&, const nsAString& aName,
+ const nsAString& aNamespaceURI) {
+ if (aName.IsEmpty()) {
+ return false;
+ }
+
+ int32_t namespaceID;
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI,
+ namespaceID);
+
+ RefPtr<nsAtom> nameElt = NS_Atomize(aName);
+ return nsContentUtils::IsCustomElementName(nameElt, namespaceID);
+}
+
+bool InspectorUtils::IsElementThemed(GlobalObject&, Element& aElement) {
+ // IsThemed will check if the native theme supports the widget using
+ // ThemeSupportsWidget which in turn will check that the widget is not
+ // already styled by content through nsNativeTheme::IsWidgetStyled. We
+ // assume that if the native theme styles the widget and the author did not
+ // override the appropriate styles, the theme will provide focus styling.
+ nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames);
+ return frame && frame->IsThemed();
+}
+
+Element* InspectorUtils::ContainingBlockOf(GlobalObject&, Element& aElement) {
+ nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames);
+ if (!frame) {
+ return nullptr;
+ }
+ nsIFrame* cb = frame->GetContainingBlock();
+ if (!cb) {
+ return nullptr;
+ }
+ return Element::FromNodeOrNull(cb->GetContent());
+}
+
+void InspectorUtils::GetBlockLineCounts(GlobalObject& aGlobal,
+ Element& aElement,
+ Nullable<nsTArray<uint32_t>>& aResult) {
+ nsBlockFrame* block =
+ do_QueryFrame(aElement.GetPrimaryFrame(FlushType::Layout));
+ if (!block) {
+ aResult.SetNull();
+ return;
+ }
+
+ // If CSS columns were specified on the actual block element (rather than an
+ // ancestor block, GetPrimaryFrame will return its ColumnSetWrapperFrame, and
+ // we need to drill down to the actual block that contains the lines.
+ if (block->IsColumnSetWrapperFrame()) {
+ nsIFrame* firstChild = block->PrincipalChildList().FirstChild();
+ if (!firstChild->IsColumnSetFrame()) {
+ aResult.SetNull();
+ return;
+ }
+ block = do_QueryFrame(firstChild->PrincipalChildList().FirstChild());
+ if (!block || block->GetContent() != &aElement) {
+ aResult.SetNull();
+ return;
+ }
+ }
+
+ nsTArray<uint32_t> result;
+ do {
+ result.AppendElement(block->Lines().size());
+ block = static_cast<nsBlockFrame*>(block->GetNextInFlow());
+ } while (block);
+
+ aResult.SetValue(std::move(result));
+}
+
+static bool FrameHasSpecifiedSize(const nsIFrame* aFrame) {
+ auto wm = aFrame->GetWritingMode();
+
+ const nsStylePosition* stylePos = aFrame->StylePosition();
+
+ return stylePos->ISize(wm).IsLengthPercentage() ||
+ stylePos->BSize(wm).IsLengthPercentage();
+}
+
+static bool IsFrameOutsideOfAncestor(const nsIFrame* aFrame,
+ const nsIFrame* aAncestorFrame,
+ const nsRect& aAncestorRect) {
+ nsRect frameRectInAncestorSpace = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, aFrame->ScrollableOverflowRect(), RelativeTo{aAncestorFrame},
+ nullptr, nullptr, false, nullptr);
+
+ // We use nsRect::SaturatingUnionEdges because it correctly handles the case
+ // of a zero-width or zero-height frame, which we still want to consider as
+ // contributing to the union.
+ nsRect unionizedRect =
+ frameRectInAncestorSpace.SaturatingUnionEdges(aAncestorRect);
+
+ // If frameRectInAncestorSpace is inside aAncestorRect then union of
+ // frameRectInAncestorSpace and aAncestorRect should be equal to aAncestorRect
+ // hence if it is equal, then false should be returned.
+
+ return !(unionizedRect == aAncestorRect);
+}
+
+static void AddOverflowingChildrenOfElement(const nsIFrame* aFrame,
+ const nsIFrame* aAncestorFrame,
+ const nsRect& aRect,
+ nsSimpleContentList& aList) {
+ MOZ_ASSERT(aFrame, "we assume the passed-in frame is non-null");
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (const nsIFrame* child : childList.mList) {
+ // We want to identify if the child or any of its children have a
+ // frame that is outside of aAncestorFrame. Ideally, child would have
+ // a frame rect that encompasses all of its children, but this is not
+ // guaranteed by the frame tree. So instead we first check other
+ // conditions that indicate child is an interesting frame:
+ //
+ // 1) child has a specified size
+ // 2) none of child's children are implicated
+ //
+ // If either of these conditions are true, we *then* check if child's
+ // frame is outside of aAncestorFrame, and if so, we add child's content
+ // to aList.
+
+ if (FrameHasSpecifiedSize(child) &&
+ IsFrameOutsideOfAncestor(child, aAncestorFrame, aRect)) {
+ aList.MaybeAppendElement(child->GetContent());
+ continue;
+ }
+
+ uint32_t currListLength = aList.Length();
+ AddOverflowingChildrenOfElement(child, aAncestorFrame, aRect, aList);
+
+ // If child is a leaf node, length of aList should remain same after
+ // calling AddOverflowingChildrenOfElement on it.
+ if (currListLength == aList.Length() &&
+ IsFrameOutsideOfAncestor(child, aAncestorFrame, aRect)) {
+ aList.MaybeAppendElement(child->GetContent());
+ }
+ }
+ }
+}
+
+already_AddRefed<nsINodeList> InspectorUtils::GetOverflowingChildrenOfElement(
+ GlobalObject& aGlobal, Element& aElement) {
+ RefPtr<nsSimpleContentList> list = new nsSimpleContentList(&aElement);
+ const nsIScrollableFrame* scrollFrame = aElement.GetScrollFrame();
+ // Element must have a nsIScrollableFrame
+ if (!scrollFrame) {
+ return list.forget();
+ }
+
+ auto scrollPortRect = scrollFrame->GetScrollPortRect();
+ const nsIFrame* outerFrame = do_QueryFrame(scrollFrame);
+ const nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
+ AddOverflowingChildrenOfElement(scrolledFrame, outerFrame, scrollPortRect,
+ *list);
+ return list.forget();
+}
+
+/* static */
+void InspectorUtils::GetRegisteredCssHighlights(GlobalObject& aGlobalObject,
+ Document& aDocument,
+ bool aActiveOnly,
+ nsTArray<nsString>& aResult) {
+ for (auto const& iter : aDocument.HighlightRegistry().HighlightsOrdered()) {
+ const RefPtr<nsAtom>& highlightName = iter.first();
+ const RefPtr<Highlight>& highlight = iter.second();
+ if (!aActiveOnly || highlight->Size() > 0) {
+ aResult.AppendElement(highlightName->GetUTF16String());
+ }
+ }
+}
+
+/* static */
+void InspectorUtils::GetCSSRegisteredProperties(
+ GlobalObject& aGlobalObject, Document& aDocument,
+ nsTArray<InspectorCSSPropertyDefinition>& aResult) {
+ nsTArray<StylePropDef> result;
+
+ ServoStyleSet& styleSet = aDocument.EnsureStyleSet();
+ // Update the rules before looking up @property rules.
+ styleSet.UpdateStylistIfNeeded();
+
+ Servo_GetRegisteredCustomProperties(styleSet.RawData(), &result);
+ for (const auto& propDef : result) {
+ InspectorCSSPropertyDefinition& property = *aResult.AppendElement();
+
+ // Servo does not include the "--" prefix in the property definition name.
+ // Add it back as it's easier for DevTools to handle them _with_ "--".
+ property.mName.AssignLiteral("--");
+ property.mName.Append(nsAtomCString(propDef.name.AsAtom()));
+ property.mSyntax.Append(propDef.syntax);
+ property.mInherits = propDef.inherits;
+ if (propDef.has_initial_value) {
+ property.mInitialValue.Append(propDef.initial_value);
+ } else {
+ property.mInitialValue.SetIsVoid(true);
+ }
+ property.mFromJS = propDef.from_js;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/layout/inspector/InspectorUtils.h b/layout/inspector/InspectorUtils.h
new file mode 100644
index 0000000000..06bc0ff5fc
--- /dev/null
+++ b/layout/inspector/InspectorUtils.h
@@ -0,0 +1,270 @@
+/* -*- 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_dom_InspectorUtils_h
+#define mozilla_dom_InspectorUtils_h
+
+#include "mozilla/dom/InspectorUtilsBinding.h"
+#include "mozilla/UniquePtr.h"
+#include "nsLayoutUtils.h"
+
+class nsAtom;
+class nsINode;
+class nsINodeList;
+class nsRange;
+
+namespace mozilla {
+class StyleSheet;
+namespace css {
+class Rule;
+} // namespace css
+namespace dom {
+class CharacterData;
+class Document;
+class Element;
+class InspectorFontFace;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+class CSSStyleRule;
+
+/**
+ * A collection of utility methods for use by devtools.
+ */
+class InspectorUtils {
+ public:
+ static void GetAllStyleSheets(GlobalObject& aGlobal, Document& aDocument,
+ bool aDocumentOnly,
+ nsTArray<RefPtr<StyleSheet>>& aResult);
+ static void GetCSSStyleRules(GlobalObject& aGlobal, Element& aElement,
+ const nsAString& aPseudo,
+ bool aIncludeVisitedStyle,
+ nsTArray<RefPtr<CSSStyleRule>>& aResult);
+
+ /**
+ * Get the line number of a rule.
+ *
+ * @param aRule The rule.
+ * @return The rule's line number. Line numbers are 1-based.
+ */
+ static uint32_t GetRuleLine(GlobalObject& aGlobal, css::Rule& aRule);
+
+ /**
+ * Get the column number of a rule.
+ *
+ * @param aRule The rule.
+ * @return The rule's column number. Column numbers are 1-based.
+ */
+ static uint32_t GetRuleColumn(GlobalObject& aGlobal, css::Rule& aRule);
+
+ /**
+ * Like getRuleLine, but if the rule is in a <style> element,
+ * returns a line number relative to the start of the element.
+ *
+ * @param aRule the rule to examine
+ * @return the line number of the rule, possibly relative to the
+ * <style> element
+ */
+ static uint32_t GetRelativeRuleLine(GlobalObject& aGlobal, css::Rule& aRule);
+
+ static void GetRuleIndex(GlobalObject& aGlobal, css::Rule& aRule,
+ nsTArray<uint32_t>& aResult);
+
+ static bool HasRulesModifiedByCSSOM(GlobalObject& aGlobal,
+ StyleSheet& aSheet);
+
+ static void GetAllStyleSheetCSSStyleRules(
+ GlobalObject& aGlobal, StyleSheet& aSheet,
+ nsTArray<RefPtr<css::Rule>>& aResult);
+
+ // Utilities for working with CSS properties
+ //
+ // Returns true if the string names a property that is inherited by default.
+ static bool IsInheritedProperty(GlobalObject& aGlobal, Document& aDocument,
+ const nsACString& aPropertyName);
+
+ // Get a list of all our supported property names. Optionally
+ // property aliases included.
+ static void GetCSSPropertyNames(GlobalObject& aGlobal,
+ const PropertyNamesOptions& aOptions,
+ nsTArray<nsString>& aResult);
+
+ // Get a list of all properties controlled by preference, as well as
+ // their corresponding preference names.
+ static void GetCSSPropertyPrefs(GlobalObject& aGlobal,
+ nsTArray<PropertyPref>& aResult);
+
+ // Get a list of all valid keywords and colors for aProperty.
+ static void GetCSSValuesForProperty(GlobalObject& aGlobal,
+ const nsACString& aPropertyName,
+ nsTArray<nsString>& aResult,
+ ErrorResult& aRv);
+
+ // Utilities for working with CSS colors
+ static void RgbToColorName(GlobalObject& aGlobal, uint8_t aR, uint8_t aG,
+ uint8_t aB, nsACString& aResult);
+
+ // Convert a given CSS color string to rgba. Returns null on failure or an
+ // InspectorRGBATuple on success.
+ //
+ // NOTE: Converting a color to RGBA may be lossy when converting from some
+ // formats e.g. CMYK.
+ static void ColorToRGBA(GlobalObject&, const nsACString& aColorString,
+ const Document*,
+ Nullable<InspectorRGBATuple>& aResult);
+
+ // Check whether a given color is a valid CSS color.
+ static bool IsValidCSSColor(GlobalObject& aGlobal,
+ const nsACString& aColorString);
+
+ // Utilities for obtaining information about a CSS property.
+
+ // Get a list of the longhands corresponding to the given CSS property. If
+ // the property is a longhand already, just returns the property itself.
+ // Throws on unsupported property names.
+ static void GetSubpropertiesForCSSProperty(GlobalObject& aGlobal,
+ const nsACString& aProperty,
+ nsTArray<nsString>& aResult,
+ ErrorResult& aRv);
+
+ // Check whether a given CSS property is a shorthand. Throws on unsupported
+ // property names.
+ static bool CssPropertyIsShorthand(GlobalObject& aGlobal,
+ const nsACString& aProperty,
+ ErrorResult& aRv);
+
+ // Check whether values of the given type are valid values for the property.
+ // For shorthands, checks whether there's a corresponding longhand property
+ // that accepts values of this type. Throws on unsupported properties or
+ // unknown types.
+ static bool CssPropertySupportsType(GlobalObject& aGlobal,
+ const nsACString& aProperty,
+ InspectorPropertyType, ErrorResult& aRv);
+
+ static bool Supports(GlobalObject&, const nsACString& aDeclaration,
+ const SupportsOptions&);
+
+ static bool IsIgnorableWhitespace(GlobalObject& aGlobalObject,
+ CharacterData& aDataNode) {
+ return IsIgnorableWhitespace(aDataNode);
+ }
+ static bool IsIgnorableWhitespace(CharacterData& aDataNode);
+
+ // Returns the "parent" of a node. The parent of a document node is the
+ // frame/iframe containing that document. aShowingAnonymousContent says
+ // whether we are showing anonymous content.
+ static nsINode* GetParentForNode(nsINode& aNode,
+ bool aShowingAnonymousContent);
+ static nsINode* GetParentForNode(GlobalObject& aGlobalObject, nsINode& aNode,
+ bool aShowingAnonymousContent) {
+ return GetParentForNode(aNode, aShowingAnonymousContent);
+ }
+
+ static void GetChildrenForNode(GlobalObject&, nsINode& aNode,
+ bool aShowingAnonymousContent,
+ bool aIncludeAssignedNodes,
+ nsTArray<RefPtr<nsINode>>& aResult) {
+ return GetChildrenForNode(aNode, aShowingAnonymousContent,
+ aIncludeAssignedNodes,
+ /* aIncludeSubdocuments = */ true, aResult);
+ }
+ static void GetChildrenForNode(nsINode& aNode, bool aShowingAnonymousContent,
+ bool aIncludeAssignedNodes,
+ bool aIncludeSubdocuments,
+ nsTArray<RefPtr<nsINode>>& aResult);
+
+ /**
+ * Setting and removing content state on an element. Both these functions
+ * call EventStateManager::SetContentState internally; the difference is
+ * that for the remove case we simply pass in nullptr for the element.
+ * Use them accordingly.
+ *
+ * When removing the active state, you may optionally also clear the active
+ * document as well by setting aClearActiveDocument
+ *
+ * @return Returns true if the state was set successfully. See more details
+ * in EventStateManager.h SetContentState.
+ */
+ static bool SetContentState(GlobalObject& aGlobal, Element& aElement,
+ uint64_t aState, ErrorResult& aRv);
+ static bool RemoveContentState(GlobalObject& aGlobal, Element& aElement,
+ uint64_t aState, bool aClearActiveDocument,
+ ErrorResult& aRv);
+ static uint64_t GetContentState(GlobalObject& aGlobal, Element& aElement);
+
+ static void GetUsedFontFaces(GlobalObject& aGlobal, nsRange& aRange,
+ uint32_t aMaxRanges, // max number of ranges to
+ // record for each face
+ bool aSkipCollapsedWhitespace,
+ nsLayoutUtils::UsedFontFaceList& aResult,
+ ErrorResult& aRv);
+
+ /**
+ * Get the names of all the supported pseudo-elements.
+ * Pseudo-elements which are only accepted in UA style sheets are
+ * not included.
+ */
+ static void GetCSSPseudoElementNames(GlobalObject& aGlobal,
+ nsTArray<nsString>& aResult);
+
+ // pseudo-class style locking methods. aPseudoClass must be a valid
+ // pseudo-class selector string, e.g. ":hover". ":any-link" and
+ // non-event-state pseudo-classes are ignored. aEnabled sets whether the
+ // psuedo-class should be locked to on or off.
+ static void AddPseudoClassLock(GlobalObject& aGlobal, Element& aElement,
+ const nsAString& aPseudoClass, bool aEnabled);
+ static void RemovePseudoClassLock(GlobalObject& aGlobal, Element& aElement,
+ const nsAString& aPseudoClass);
+ static bool HasPseudoClassLock(GlobalObject& aGlobal, Element& aElement,
+ const nsAString& aPseudoClass);
+ static void ClearPseudoClassLocks(GlobalObject& aGlobal, Element& aElement);
+
+ static bool IsElementThemed(GlobalObject& aGlobal, Element& aElement);
+
+ static Element* ContainingBlockOf(GlobalObject&, Element&);
+
+ static void GetBlockLineCounts(GlobalObject& aGlobal, Element& aElement,
+ Nullable<nsTArray<uint32_t>>& aResult);
+
+ MOZ_CAN_RUN_SCRIPT
+ static already_AddRefed<nsINodeList> GetOverflowingChildrenOfElement(
+ GlobalObject& aGlobal, Element& element);
+
+ /**
+ * Parse CSS and update the style sheet in place.
+ *
+ * @param DOMCSSStyleSheet aSheet
+ * @param UTF8String aInput
+ * The new source string for the style sheet.
+ */
+ static void ParseStyleSheet(GlobalObject& aGlobal, StyleSheet& aSheet,
+ const nsACString& aInput, ErrorResult& aRv);
+
+ /**
+ * Check if the provided name can be custom element name.
+ */
+ static bool IsCustomElementName(GlobalObject&, const nsAString& aName,
+ const nsAString& aNamespaceURI);
+
+ /**
+ * Get the names of registered Highlights
+ */
+ static void GetRegisteredCssHighlights(GlobalObject& aGlobal,
+ Document& aDocument, bool aActiveOnly,
+ nsTArray<nsString>& aResult);
+ /**
+ * Get registered CSS properties (via CSS.registerProperty or @property)
+ */
+ static void GetCSSRegisteredProperties(
+ GlobalObject& aGlobal, Document& aDocument,
+ nsTArray<InspectorCSSPropertyDefinition>& aResult);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_InspectorUtils_h
diff --git a/layout/inspector/ServoStyleRuleMap.cpp b/layout/inspector/ServoStyleRuleMap.cpp
new file mode 100644
index 0000000000..b67d0c98fc
--- /dev/null
+++ b/layout/inspector/ServoStyleRuleMap.cpp
@@ -0,0 +1,166 @@
+/* -*- 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/ServoStyleRuleMap.h"
+
+#include "mozilla/css/GroupRule.h"
+#include "mozilla/dom/CSSImportRule.h"
+#include "mozilla/dom/CSSRuleBinding.h"
+#include "mozilla/dom/CSSStyleRule.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "nsStyleSheetService.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+void ServoStyleRuleMap::EnsureTable(ServoStyleSet& aStyleSet) {
+ if (!IsEmpty()) {
+ return;
+ }
+ aStyleSet.EnumerateStyleSheets(
+ [&](StyleSheet& aSheet) { FillTableFromStyleSheet(aSheet); });
+}
+
+void ServoStyleRuleMap::EnsureTable(ShadowRoot& aShadowRoot) {
+ if (!IsEmpty()) {
+ return;
+ }
+ for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
+ FillTableFromStyleSheet(*aShadowRoot.SheetAt(index));
+ }
+ for (const auto& sheet : aShadowRoot.AdoptedStyleSheets()) {
+ FillTableFromStyleSheet(*sheet);
+ }
+}
+
+void ServoStyleRuleMap::SheetAdded(StyleSheet& aStyleSheet) {
+ if (!IsEmpty()) {
+ FillTableFromStyleSheet(aStyleSheet);
+ }
+}
+
+void ServoStyleRuleMap::SheetCloned(StyleSheet& aStyleSheet) {
+ // Invalidate all data inside. We could probably track down all the individual
+ // rules that changed etc, but it doesn't seem worth it.
+ //
+ // TODO: We can't do this until GetCssRulesInternal stops cloning.
+ // mTable.Clear();
+}
+
+void ServoStyleRuleMap::SheetRemoved(StyleSheet& aStyleSheet) {
+ // Invalidate all data inside. This isn't strictly necessary since
+ // we should always get update from document before new queries come.
+ // But it is probably still safer if we try to avoid having invalid
+ // pointers inside. Also if the document keep adding and removing
+ // stylesheets, this would also prevent us from infinitely growing
+ // memory usage.
+ mTable.Clear();
+}
+
+void ServoStyleRuleMap::RuleAdded(StyleSheet& aStyleSheet,
+ css::Rule& aStyleRule) {
+ if (!IsEmpty()) {
+ FillTableFromRule(aStyleRule);
+ }
+}
+
+void ServoStyleRuleMap::RuleRemoved(StyleSheet& aStyleSheet,
+ css::Rule& aStyleRule) {
+ if (IsEmpty()) {
+ return;
+ }
+
+ switch (aStyleRule.Type()) {
+ case StyleCssRuleType::Style:
+ case StyleCssRuleType::Import:
+ case StyleCssRuleType::Media:
+ case StyleCssRuleType::Supports:
+ case StyleCssRuleType::LayerBlock:
+ case StyleCssRuleType::Container:
+ case StyleCssRuleType::Document: {
+ // See the comment in SheetRemoved.
+ mTable.Clear();
+ break;
+ }
+ case StyleCssRuleType::LayerStatement:
+ case StyleCssRuleType::FontFace:
+ case StyleCssRuleType::Page:
+ case StyleCssRuleType::Property:
+ case StyleCssRuleType::Keyframes:
+ case StyleCssRuleType::Keyframe:
+ case StyleCssRuleType::Margin:
+ case StyleCssRuleType::Namespace:
+ case StyleCssRuleType::CounterStyle:
+ case StyleCssRuleType::FontFeatureValues:
+ case StyleCssRuleType::FontPaletteValues:
+ break;
+ }
+}
+
+size_t ServoStyleRuleMap::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+void ServoStyleRuleMap::FillTableFromRule(css::Rule& aRule) {
+ switch (aRule.Type()) {
+ case StyleCssRuleType::Style: {
+ auto& rule = static_cast<CSSStyleRule&>(aRule);
+ mTable.InsertOrUpdate(rule.Raw(), &rule);
+ [[fallthrough]];
+ }
+ case StyleCssRuleType::LayerBlock:
+ case StyleCssRuleType::Media:
+ case StyleCssRuleType::Supports:
+ case StyleCssRuleType::Container:
+ case StyleCssRuleType::Document: {
+ auto& rule = static_cast<css::GroupRule&>(aRule);
+ FillTableFromRuleList(*rule.CssRules());
+ break;
+ }
+ case StyleCssRuleType::Import: {
+ auto& rule = static_cast<CSSImportRule&>(aRule);
+ if (auto* sheet = rule.GetStyleSheet()) {
+ FillTableFromStyleSheet(*sheet);
+ }
+ break;
+ }
+ case StyleCssRuleType::LayerStatement:
+ case StyleCssRuleType::FontFace:
+ case StyleCssRuleType::Page:
+ case StyleCssRuleType::Property:
+ case StyleCssRuleType::Keyframes:
+ case StyleCssRuleType::Keyframe:
+ case StyleCssRuleType::Margin:
+ case StyleCssRuleType::Namespace:
+ case StyleCssRuleType::CounterStyle:
+ case StyleCssRuleType::FontFeatureValues:
+ case StyleCssRuleType::FontPaletteValues:
+ break;
+ }
+}
+
+void ServoStyleRuleMap::FillTableFromRuleList(ServoCSSRuleList& aRuleList) {
+ for (uint32_t i : IntegerRange(aRuleList.Length())) {
+ FillTableFromRule(*aRuleList.GetRule(i));
+ }
+}
+
+void ServoStyleRuleMap::FillTableFromStyleSheet(StyleSheet& aSheet) {
+ if (aSheet.IsComplete()) {
+ FillTableFromRuleList(*aSheet.GetCssRulesInternal());
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/inspector/ServoStyleRuleMap.h b/layout/inspector/ServoStyleRuleMap.h
new file mode 100644
index 0000000000..09d5dae260
--- /dev/null
+++ b/layout/inspector/ServoStyleRuleMap.h
@@ -0,0 +1,65 @@
+/* -*- 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_ServoStyleRuleMap_h
+#define mozilla_ServoStyleRuleMap_h
+
+#include "mozilla/dom/CSSStyleRule.h"
+#include "mozilla/StyleSheet.h"
+
+#include "nsTHashMap.h"
+
+struct StyleLockedStyleRule;
+
+namespace mozilla {
+class ServoCSSRuleList;
+class ServoStyleSet;
+namespace css {
+class Rule;
+} // namespace css
+namespace dom {
+class ShadowRoot;
+}
+class ServoStyleRuleMap {
+ public:
+ ServoStyleRuleMap() = default;
+
+ void EnsureTable(ServoStyleSet&);
+ void EnsureTable(dom::ShadowRoot&);
+
+ dom::CSSStyleRule* Lookup(const StyleLockedStyleRule* aRawRule) const {
+ return mTable.Get(aRawRule);
+ }
+
+ void SheetAdded(StyleSheet&);
+ void SheetRemoved(StyleSheet&);
+ void SheetCloned(StyleSheet&);
+
+ void RuleAdded(StyleSheet& aStyleSheet, css::Rule&);
+ void RuleRemoved(StyleSheet& aStyleSheet, css::Rule&);
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ ~ServoStyleRuleMap() = default;
+
+ private:
+ // Since we would never have a document which contains no style rule,
+ // we use IsEmpty as an indication whether we need to walk through
+ // all stylesheets to fill the table.
+ bool IsEmpty() const { return mTable.Count() == 0; }
+
+ void FillTableFromRule(css::Rule&);
+ void FillTableFromRuleList(ServoCSSRuleList&);
+ void FillTableFromStyleSheet(StyleSheet&);
+
+ using Hashtable = nsTHashMap<nsPtrHashKey<const StyleLockedStyleRule>,
+ WeakPtr<dom::CSSStyleRule>>;
+ Hashtable mTable;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ServoStyleRuleMap_h
diff --git a/layout/inspector/inDeepTreeWalker.cpp b/layout/inspector/inDeepTreeWalker.cpp
new file mode 100644
index 0000000000..07b866887f
--- /dev/null
+++ b/layout/inspector/inDeepTreeWalker.cpp
@@ -0,0 +1,334 @@
+/* -*- 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 "inDeepTreeWalker.h"
+#include "inLayoutUtils.h"
+
+#include "mozilla/Try.h"
+#include "mozilla/dom/CSSStyleRule.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/InspectorUtils.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+
+#include "nsString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIContent.h"
+#include "ChildIterator.h"
+
+using mozilla::dom::InspectorUtils;
+
+/*****************************************************************************
+ * This implementation does not currently operaate according to the W3C spec.
+ * In particular it does NOT handle DOM mutations during the walk. It also
+ * ignores whatToShow and the filter.
+ *****************************************************************************/
+
+////////////////////////////////////////////////////
+
+inDeepTreeWalker::inDeepTreeWalker() = default;
+inDeepTreeWalker::~inDeepTreeWalker() = default;
+
+NS_IMPL_ISUPPORTS(inDeepTreeWalker, inIDeepTreeWalker)
+
+////////////////////////////////////////////////////
+// inIDeepTreeWalker
+
+NS_IMETHODIMP
+inDeepTreeWalker::GetShowAnonymousContent(bool* aShowAnonymousContent) {
+ *aShowAnonymousContent = mShowAnonymousContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::SetShowAnonymousContent(bool aShowAnonymousContent) {
+ mShowAnonymousContent = aShowAnonymousContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::GetShowSubDocuments(bool* aShowSubDocuments) {
+ *aShowSubDocuments = mShowSubDocuments;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::SetShowSubDocuments(bool aShowSubDocuments) {
+ mShowSubDocuments = aShowSubDocuments;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::GetShowDocumentsAsNodes(bool* aShowDocumentsAsNodes) {
+ *aShowDocumentsAsNodes = mShowDocumentsAsNodes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::SetShowDocumentsAsNodes(bool aShowDocumentsAsNodes) {
+ mShowDocumentsAsNodes = aShowDocumentsAsNodes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::Init(nsINode* aRoot) {
+ if (!aRoot) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mRoot = aRoot;
+ mCurrentNode = aRoot;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+inDeepTreeWalker::GetRoot(nsINode** aRoot) {
+ *aRoot = mRoot;
+ NS_IF_ADDREF(*aRoot);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::GetCurrentNode(nsINode** aCurrentNode) {
+ *aCurrentNode = mCurrentNode;
+ NS_IF_ADDREF(*aCurrentNode);
+ return NS_OK;
+}
+
+already_AddRefed<nsINode> inDeepTreeWalker::GetParent() {
+ MOZ_ASSERT(mCurrentNode);
+
+ if (mCurrentNode == mRoot) {
+ return nullptr;
+ }
+
+ nsINode* parentNode =
+ InspectorUtils::GetParentForNode(*mCurrentNode, mShowAnonymousContent);
+ if (!parentNode) {
+ return nullptr;
+ }
+
+ // For compatibility reasons by default we skip the document nodes
+ // from the walk.
+ if (!mShowDocumentsAsNodes && parentNode->IsDocument() &&
+ parentNode != mRoot) {
+ parentNode =
+ InspectorUtils::GetParentForNode(*parentNode, mShowAnonymousContent);
+ }
+
+ return do_AddRef(parentNode);
+}
+
+void inDeepTreeWalker::GetChildren(nsINode& aParent, ChildList& aChildList) {
+ aChildList.ClearAndRetainStorage();
+ InspectorUtils::GetChildrenForNode(aParent, mShowAnonymousContent,
+ /* aIncludeAssignedNodes = */ false,
+ mShowSubDocuments, aChildList);
+ if (aChildList.Length() == 1 && aChildList.ElementAt(0)->IsDocument() &&
+ !mShowDocumentsAsNodes) {
+ RefPtr parent = aChildList.ElementAt(0);
+ aChildList.ClearAndRetainStorage();
+ InspectorUtils::GetChildrenForNode(*parent, mShowAnonymousContent,
+ /* aIncludeAssignedNodes = */ false,
+ mShowSubDocuments, aChildList);
+ }
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::SetCurrentNode(nsINode* aCurrentNode) {
+ // mCurrentNode can only be null if init either failed, or has not been called
+ // yet.
+ if (!mCurrentNode || !aCurrentNode) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If Document nodes are skipped by the walk, we should not allow one to set
+ // one as the current node either.
+ if (!mShowDocumentsAsNodes) {
+ if (aCurrentNode->IsDocument()) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // We want to store the original state so in case of error
+ // we can restore that.
+ ChildList oldSiblings;
+ mSiblings.SwapElements(oldSiblings);
+ nsCOMPtr<nsINode> oldCurrent = std::move(mCurrentNode);
+
+ mCurrentNode = aCurrentNode;
+ if (RefPtr<nsINode> parent = GetParent()) {
+ GetChildren(*parent, mSiblings);
+ // We cached all the siblings (if there are any) of the current node, but we
+ // still have to set the index too, to be able to iterate over them.
+ int32_t index = mSiblings.IndexOf(mCurrentNode);
+ if (index < 0) {
+ // If someone tries to set current node to some value that is not
+ // reachable otherwise, let's throw. (For example mShowAnonymousContent is
+ // false and some NAC was passed in).
+ // Restore state first.
+ mCurrentNode = std::move(oldCurrent);
+ oldSiblings.SwapElements(mSiblings);
+ return NS_ERROR_INVALID_ARG;
+ }
+ mCurrentIndex = index;
+ } else {
+ mCurrentIndex = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::ParentNode(nsINode** _retval) {
+ *_retval = nullptr;
+ if (!mCurrentNode || mCurrentNode == mRoot) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> parent = GetParent();
+ if (!parent) {
+ return NS_OK;
+ }
+
+ MOZ_TRY(SetCurrentNode(parent));
+
+ parent.forget(_retval);
+ return NS_OK;
+}
+
+// FirstChild and LastChild are very similar methods, this is the generic
+// version for internal use. With aReverse = true it returns the LastChild.
+nsresult inDeepTreeWalker::EdgeChild(nsINode** _retval, bool aFront) {
+ if (!mCurrentNode) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *_retval = nullptr;
+
+ ChildList children;
+ GetChildren(*mCurrentNode, children);
+ if (children.IsEmpty()) {
+ return NS_OK;
+ }
+ mSiblings = std::move(children);
+ mCurrentIndex = aFront ? 0 : mSiblings.Length() - 1;
+ mCurrentNode = mSiblings.ElementAt(mCurrentIndex);
+ NS_ADDREF(*_retval = mCurrentNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::FirstChild(nsINode** _retval) {
+ return EdgeChild(_retval, /* aFront = */ true);
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::LastChild(nsINode** _retval) {
+ return EdgeChild(_retval, /* aFront = */ false);
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::PreviousSibling(nsINode** _retval) {
+ *_retval = nullptr;
+ if (!mCurrentNode || mCurrentIndex < 1) {
+ return NS_OK;
+ }
+
+ nsINode* prev = mSiblings.ElementAt(--mCurrentIndex);
+ mCurrentNode = prev;
+ NS_ADDREF(*_retval = mCurrentNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::NextSibling(nsINode** _retval) {
+ *_retval = nullptr;
+ if (!mCurrentNode || mCurrentIndex + 1 >= (int32_t)mSiblings.Length()) {
+ return NS_OK;
+ }
+
+ nsINode* next = mSiblings.ElementAt(++mCurrentIndex);
+ mCurrentNode = next;
+ NS_ADDREF(*_retval = mCurrentNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::PreviousNode(nsINode** _retval) {
+ if (!mCurrentNode || mCurrentNode == mRoot) {
+ // Nowhere to go from here
+ *_retval = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> node;
+ PreviousSibling(getter_AddRefs(node));
+
+ if (!node) {
+ return ParentNode(_retval);
+ }
+
+ // Now we're positioned at our previous sibling. But since the DOM tree
+ // traversal is depth-first, the previous node is its most deeply nested last
+ // child. Just loop until LastChild() returns null; since the LastChild()
+ // call that returns null won't affect our position, we will then be
+ // positioned at the correct node.
+ while (node) {
+ LastChild(getter_AddRefs(node));
+ }
+
+ NS_ADDREF(*_retval = mCurrentNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+inDeepTreeWalker::NextNode(nsINode** _retval) {
+ if (!mCurrentNode) {
+ return NS_OK;
+ }
+
+ // First try our kids
+ FirstChild(_retval);
+
+ if (*_retval) {
+ return NS_OK;
+ }
+
+ // Now keep trying next siblings up the parent chain, but if we
+ // discover there's nothing else restore our state.
+#ifdef DEBUG
+ nsINode* origCurrentNode = mCurrentNode;
+#endif
+ uint32_t lastChildCallsToMake = 0;
+ while (1) {
+ NextSibling(_retval);
+
+ if (*_retval) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> parent;
+ ParentNode(getter_AddRefs(parent));
+ if (!parent) {
+ // Nowhere else to go; we're done. Restore our state.
+ while (lastChildCallsToMake--) {
+ nsCOMPtr<nsINode> dummy;
+ LastChild(getter_AddRefs(dummy));
+ }
+ NS_ASSERTION(mCurrentNode == origCurrentNode,
+ "Didn't go back to the right node?");
+ *_retval = nullptr;
+ return NS_OK;
+ }
+ ++lastChildCallsToMake;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("how did we get here?");
+ return NS_OK;
+}
diff --git a/layout/inspector/inDeepTreeWalker.h b/layout/inspector/inDeepTreeWalker.h
new file mode 100644
index 0000000000..b421e0831b
--- /dev/null
+++ b/layout/inspector/inDeepTreeWalker.h
@@ -0,0 +1,61 @@
+/* -*- 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 __inDeepTreeWalker_h___
+#define __inDeepTreeWalker_h___
+
+#include "inIDeepTreeWalker.h"
+
+#include "nsCOMPtr.h"
+#include "nsINode.h"
+#include "nsTArray.h"
+
+class nsINodeList;
+
+class inDeepTreeWalker final : public inIDeepTreeWalker {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_INIDEEPTREEWALKER
+
+ inDeepTreeWalker();
+
+ using ChildList = AutoTArray<RefPtr<nsINode>, 8>;
+ void GetChildren(nsINode& aParent, ChildList&);
+
+ protected:
+ virtual ~inDeepTreeWalker();
+
+ already_AddRefed<nsINode> GetParent();
+ nsresult EdgeChild(nsINode** _retval, bool aReverse);
+
+ bool mShowAnonymousContent = false;
+ bool mShowSubDocuments = false;
+ bool mShowDocumentsAsNodes = false;
+
+ // The root node. previousNode and parentNode will return
+ // null from here.
+ nsCOMPtr<nsINode> mRoot;
+ nsCOMPtr<nsINode> mCurrentNode;
+
+ // We cache the siblings of mCurrentNode as a list of nodes.
+ // Notes: normally siblings are all the children of the parent
+ // of mCurrentNode (that are interesting for use for the walk)
+ // and mCurrentIndex is the index of mCurrentNode in that list
+ ChildList mSiblings;
+
+ // Index of mCurrentNode in the mSiblings list.
+ int32_t mCurrentIndex = -1;
+};
+
+// {BFCB82C2-5611-4318-90D6-BAF4A7864252}
+#define IN_DEEPTREEWALKER_CID \
+ { \
+ 0xbfcb82c2, 0x5611, 0x4318, { \
+ 0x90, 0xd6, 0xba, 0xf4, 0xa7, 0x86, 0x42, 0x52 \
+ } \
+ }
+
+#endif // __inDeepTreeWalker_h___
diff --git a/layout/inspector/inIDeepTreeWalker.idl b/layout/inspector/inIDeepTreeWalker.idl
new file mode 100644
index 0000000000..1cbd5ab039
--- /dev/null
+++ b/layout/inspector/inIDeepTreeWalker.idl
@@ -0,0 +1,43 @@
+/* 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 "nsISupports.idl"
+
+webidl Node;
+
+// Note: the iterator does not handle DOM mutations gracefully. So if
+// the underlying DOM we are iterating over is changed, the behavior
+// of the walker is undefined. (With the current implementation we
+// cache the siblings of the current node and this list is not updated
+// when a mutation occurs).
+
+[scriptable, uuid(6657e8eb-b646-48e7-993e-cfa6e96415b4)]
+interface inIDeepTreeWalker : nsISupports
+{
+ attribute boolean showAnonymousContent;
+ attribute boolean showSubDocuments;
+
+ // By default the walker skips document nodes from the iteration,
+ // by setting this flag to true this behavior can be altered.
+ attribute boolean showDocumentsAsNodes;
+
+ void init(in Node aRoot);
+
+ // Methods and attributes that look like TreeWalker.
+ // Note: normally parentNode cannot go further up on the tree once it reached
+ // the root, but setting currentNode does not have this limitation. If currentNode
+ // is set to a node that does not have the root as its ancestor the walk can be
+ // continued from there, and once we reach a node that is 'under' the root, the
+ // limitation for the parentNode will work again.
+ readonly attribute Node root;
+ attribute Node currentNode;
+
+ Node parentNode();
+ Node firstChild();
+ Node lastChild();
+ Node previousSibling();
+ Node nextSibling();
+ Node previousNode();
+ Node nextNode();
+};
diff --git a/layout/inspector/inLayoutUtils.cpp b/layout/inspector/inLayoutUtils.cpp
new file mode 100644
index 0000000000..9a158eb900
--- /dev/null
+++ b/layout/inspector/inLayoutUtils.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "inLayoutUtils.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsIContent.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+///////////////////////////////////////////////////////////////////////////////
+
+EventStateManager* inLayoutUtils::GetEventStateManagerFor(Element& aElement) {
+ Document* doc = aElement.OwnerDoc();
+ nsPresContext* presContext = doc->GetPresContext();
+ if (!presContext) return nullptr;
+
+ return presContext->EventStateManager();
+}
+
+Document* inLayoutUtils::GetSubDocumentFor(nsINode* aNode) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
+ if (content) {
+ nsCOMPtr<Document> doc = content->GetComposedDoc();
+ if (doc) {
+ return doc->GetSubDocumentFor(content);
+ }
+ }
+
+ return nullptr;
+}
+
+nsINode* inLayoutUtils::GetContainerFor(const Document& aDoc) {
+ nsPIDOMWindowOuter* pwin = aDoc.GetWindow();
+ if (!pwin) {
+ return nullptr;
+ }
+
+ return pwin->GetFrameElementInternal();
+}
diff --git a/layout/inspector/inLayoutUtils.h b/layout/inspector/inLayoutUtils.h
new file mode 100644
index 0000000000..3bc8a36a7e
--- /dev/null
+++ b/layout/inspector/inLayoutUtils.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 __inLayoutUtils_h__
+#define __inLayoutUtils_h__
+
+class nsINode;
+
+namespace mozilla {
+class EventStateManager;
+namespace dom {
+class Document;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class inLayoutUtils {
+ public:
+ static mozilla::EventStateManager* GetEventStateManagerFor(
+ mozilla::dom::Element& aElement);
+ static mozilla::dom::Document* GetSubDocumentFor(nsINode* aNode);
+ static nsINode* GetContainerFor(const mozilla::dom::Document& aDoc);
+};
+
+#endif // __inLayoutUtils_h__
diff --git a/layout/inspector/moz.build b/layout/inspector/moz.build
new file mode 100644
index 0000000000..45d31eb090
--- /dev/null
+++ b/layout/inspector/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG["ENABLE_TESTS"]:
+ MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
+ MOCHITEST_MANIFESTS += ["tests/mochitest.toml"]
+
+XPIDL_SOURCES += [
+ "inIDeepTreeWalker.idl",
+]
+
+XPIDL_MODULE = "inspector"
+
+EXPORTS.mozilla += [
+ "ServoStyleRuleMap.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "InspectorFontFace.h",
+ "InspectorUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "inDeepTreeWalker.cpp",
+ "inLayoutUtils.cpp",
+ "InspectorFontFace.cpp",
+ "InspectorUtils.cpp",
+ "ServoStyleRuleMap.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "../style",
+ "/dom/base",
+]
diff --git a/layout/inspector/tests/bug1202095-2.css b/layout/inspector/tests/bug1202095-2.css
new file mode 100644
index 0000000000..a484814ebf
--- /dev/null
+++ b/layout/inspector/tests/bug1202095-2.css
@@ -0,0 +1,7 @@
+/*
+ * Bug 1202095 - parseStyleSheet should not re-load @imports
+ */
+
+body {
+ color: chartreuse;
+}
diff --git a/layout/inspector/tests/bug1202095.css b/layout/inspector/tests/bug1202095.css
new file mode 100644
index 0000000000..fa8ef0feb6
--- /dev/null
+++ b/layout/inspector/tests/bug1202095.css
@@ -0,0 +1,7 @@
+/*
+ * Bug 1202095 - parseStyleSheet should not re-load @imports
+ */
+
+body {
+ background-color: purple;
+}
diff --git a/layout/inspector/tests/bug856317.css b/layout/inspector/tests/bug856317.css
new file mode 100644
index 0000000000..49691e3ebe
--- /dev/null
+++ b/layout/inspector/tests/bug856317.css
@@ -0,0 +1,23 @@
+/*
+ * Bug 856317 - expose the column number of style rules via inIDOMUtils
+ */
+
+/* simplest possible */
+.alpha {
+}
+
+/* with leading whitespace */
+ #beta {
+ }
+
+/* with a comment before the rule */ #gamma {
+}
+
+/* mixed spaces and tab characters */
+ #delta {
+}
+
+/* long lines, like those produced by CSS minifiers
+ * (overflows a 16-bit unsigned int)
+ */
+ .epsilon{ background-image: url("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.png")}.zeta,.eta{}
diff --git a/layout/inspector/tests/chrome/DejaVuSans.ttf b/layout/inspector/tests/chrome/DejaVuSans.ttf
new file mode 100644
index 0000000000..84ca1d7503
--- /dev/null
+++ b/layout/inspector/tests/chrome/DejaVuSans.ttf
Binary files differ
diff --git a/layout/inspector/tests/chrome/GentiumPlus-R.woff b/layout/inspector/tests/chrome/GentiumPlus-R.woff
new file mode 100644
index 0000000000..ebefd081a8
--- /dev/null
+++ b/layout/inspector/tests/chrome/GentiumPlus-R.woff
Binary files differ
diff --git a/layout/inspector/tests/chrome/chrome.toml b/layout/inspector/tests/chrome/chrome.toml
new file mode 100644
index 0000000000..193be351cb
--- /dev/null
+++ b/layout/inspector/tests/chrome/chrome.toml
@@ -0,0 +1,43 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+support-files = ["GentiumPlus-R.woff"]
+
+["test_bug467669.xhtml"]
+support-files = ["test_bug467669.css"]
+
+["test_bug695639.xhtml"]
+support-files = ["test_bug695639.css"]
+
+["test_bug708874.xhtml"]
+support-files = ["test_bug708874.css"]
+
+["test_bug727834.xhtml"]
+support-files = ["test_bug727834.css"]
+
+["test_fontFaceGeneric.xhtml"]
+
+["test_fontFaceRanges.xhtml"]
+support-files = ["test_fontFaceRanges.css"]
+
+["test_fontFeaturesAPI.xhtml"]
+support-files = [
+ "test_fontFeaturesAPI.css",
+ "DejaVuSans.ttf",
+]
+
+["test_fontVariationsAPI.xhtml"]
+skip-if = [
+ "win11_2009 && bits == 32",
+ "apple_catalina", # bug 1456856
+]
+support-files = ["test_fontVariationsAPI.css"]
+
+["test_getBlockLineCounts.html"]
+
+["test_getSelectorWarnings.html"]
+
+["test_ua_rule_modification.html"]
+
+["test_ua_sheet_disable.html"]
+
+["test_visited_style.html"]
diff --git a/layout/inspector/tests/chrome/test_bug467669.css b/layout/inspector/tests/chrome/test_bug467669.css
new file mode 100644
index 0000000000..fb050bf319
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug467669.css
@@ -0,0 +1,8 @@
+@font-face {
+ font-family: font-face-test-family;
+ src: url(bad/font/name.ttf), url(GentiumPlus-R.woff) format("woff");
+}
+
+.gentium {
+ font-family: font-face-test-family;
+}
diff --git a/layout/inspector/tests/chrome/test_bug467669.xhtml b/layout/inspector/tests/chrome/test_bug467669.xhtml
new file mode 100644
index 0000000000..01db241662
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug467669.xhtml
@@ -0,0 +1,168 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_bug467669.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=467669
+-->
+<window title="Mozilla Bug 467669"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 467669 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function RunTest() {
+ const kIsLinux = navigator.platform.indexOf("Linux") == 0;
+ const kIsMac = navigator.platform.indexOf("Mac") == 0;
+ const kIsWin = navigator.platform.indexOf("Win") == 0;
+
+ var rng = document.createRange();
+ var elem, fonts, f;
+
+ elem = document.getElementById("test1");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts for simple Latin text");
+ f = fonts[0];
+ is(f.rule, null, "rule");
+ is(f.srcIndex, -1, "srcIndex");
+ is(f.localName, "", "local name");
+ is(f.URI, "", "URI");
+ is(f.format, "", "format string");
+ is(f.metadata, "", "metadata");
+// report(elem.id, fonts);
+
+ elem = document.getElementById("test2");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 3, "number of fonts for mixed serif, sans and monospaced text");
+// report(elem.id, fonts);
+
+ elem = document.getElementById("test3");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 2, "number of fonts for mixed Latin & Chinese");
+// report(elem.id, fonts);
+
+ // get properties of a @font-face font
+ elem = document.getElementById("test4");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts in @font-face test");
+ f = fonts[0];
+ isnot(f.rule, null, "missing rule");
+ is(f.srcIndex, 1, "srcIndex");
+ is(f.localName, "", "local name");
+ is(f.URI, "chrome://mochitests/content/chrome/layout/inspector/tests/chrome/GentiumPlus-R.woff", "bad URI");
+ is(f.format, "woff", "format");
+ is(/bukva:raz/.test(f.metadata), true, "metadata");
+// report(elem.id, fonts);
+
+ elem = document.getElementById("test5").childNodes[0];
+ // check that string length is as expected, including soft hyphens
+ is(elem.length, 42, "string length with soft hyphens");
+
+ // initial latin substring...
+ rng.setStart(elem, 0);
+ rng.setEnd(elem, 20); // "supercalifragilistic"
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts (Latin-only)");
+ f = fonts[0];
+ is(f.name, "Gentium Plus", "font name");
+ is(f.CSSFamilyName, "font-face-test-family", "family name");
+ is(f.fromFontGroup, true, "font matched in font group");
+
+ // extend to include a chinese character
+ rng.setEnd(elem, 21);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 2, "number of fonts (incl Chinese)");
+ if (kIsMac || kIsWin) { // these are only implemented by the Mac & Win font backends
+ var i;
+ for (i = 0; i < fonts.length; ++i) {
+ f = fonts[i];
+ if (f.rule) {
+ is(f.fromFontGroup, true, "@font-face font matched in group");
+ is(f.fromLanguagePrefs, false, "not from language prefs");
+ is(f.fromSystemFallback, false, "not from system fallback");
+ } else {
+ is(f.fromFontGroup, false, "not matched in group");
+ is(f.fromLanguagePrefs, true, "from language prefs");
+ is(f.fromSystemFallback, false, "not from system fallback");
+ }
+ }
+ }
+
+ // second half of the string includes &shy; chars to check original/skipped mapping;
+ // select just the final character
+ rng.setStart(elem, elem.length - 1);
+ rng.setEnd(elem, elem.length);
+ is(rng.toString(), "!", "content of range");
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts for last char");
+ f = fonts[0];
+ is(f.name, "Gentium Plus", "font name");
+
+ // include the preceding character as well
+ rng.setStart(elem, elem.length - 2);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 2, "number of fonts for last two chars");
+
+ // then trim the final one
+ rng.setEnd(elem, elem.length - 1);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts for Chinese char");
+ f = fonts[0];
+ isnot(f.name, "Gentium Plus", "font name for Chinese char");
+
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+// report("test5", fonts);
+
+ elem = document.getElementById("test6");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 2, "number of font faces for regular & italic");
+ is(fonts[0].CSSFamilyName, fonts[1].CSSFamilyName, "same family for regular & italic");
+ isnot(fonts[0].name, fonts[1].name, "different faces for regular & italic");
+// report(elem.id, fonts);
+
+ SimpleTest.finish();
+}
+
+// just for test-debugging purposes
+function report(e, f) {
+ var fontNames = "";
+ var i;
+ for (i = 0; i < f.length; ++i) {
+ if (i == 0) {
+ fontNames += e + " fonts: "
+ } else {
+ fontNames += ", ";
+ }
+ fontNames += f.item(i).name;
+ }
+ dump(fontNames + "\n");
+}
+
+ ]]>
+ </script>
+
+ <!-- html:body contains elements the test will inspect -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467669"
+ target="_blank">Mozilla Bug 467669</a>
+ <div id="test1">Hello world</div>
+ <div id="test2" style="font-family:sans-serif"><span style="font-family:serif">Hello</span> <tt>cruel</tt> world</div>
+ <div id="test3">Hello, &#x4F60;&#x597D;</div>
+ <div id="test4" class="gentium">Hello Gentium Plus!</div>
+ <div id="test5" class="gentium">supercalifragilistic&#x4F60;ex&#xAD;pi&#xAD;a&#xAD;li&#xAD;do&#xAD;cious&#x597D;!</div>
+ <div id="test6" style="font-family:serif">regular and <em>italic</em> text</div>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_bug695639.css b/layout/inspector/tests/chrome/test_bug695639.css
new file mode 100644
index 0000000000..549537498c
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug695639.css
@@ -0,0 +1,8 @@
+@font-face {
+ font-family: gent;
+ src: url(GentiumPlus-R.woff) format("woff");
+}
+
+.test {
+ font-family: gent, sans-serif;
+}
diff --git a/layout/inspector/tests/chrome/test_bug695639.xhtml b/layout/inspector/tests/chrome/test_bug695639.xhtml
new file mode 100644
index 0000000000..ff84f079f7
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug695639.xhtml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_bug695639.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=695639
+-->
+<window title="Mozilla Bug 695639"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 695639 - check that GetFontFacesForText handles wrapped lines properly **/
+
+SimpleTest.waitForExplicitFinish();
+
+function RunTest() {
+ var rng = document.createRange();
+ var elem, fonts, f;
+
+ elem = document.getElementById("test").childNodes[0];
+ rng.setStart(elem, 0);
+ rng.setEnd(elem, 14);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 2, "number of fonts used for entire text");
+
+ // initial latin substring...
+ rng.setStart(elem, 0);
+ rng.setEnd(elem, 5); // "Hello"
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts (1)");
+ f = fonts[0];
+ is(f.name, "Gentium Plus", "font name (1)");
+
+ // the space (where the line wraps) should also be Gentium
+ rng.setStart(elem, 5);
+ rng.setEnd(elem, 6); // space
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts (2)");
+ f = fonts[0];
+ is(f.name, "Gentium Plus", "font name (2)");
+
+ // the Chinese text "ni hao" should NOT be in Gentium
+ rng.setStart(elem, 6);
+ rng.setEnd(elem, 8); // two Chinese characters on second line
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts (3)");
+ f = fonts[0];
+ isnot(f.name, "Gentium Plus", "font name (3)");
+
+ // space and "world" should be Gentium again
+ rng.setStart(elem, 8);
+ rng.setEnd(elem, 14);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts (4)");
+ f = fonts[0];
+ is(f.name, "Gentium Plus", "font name (4)");
+
+ SimpleTest.finish();
+}
+ ]]>
+ </script>
+
+ <style type="text/css">
+ </style>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=695639"
+ target="_blank">Mozilla Bug 695639</a>
+ <div style="width: 2em;" class="test" id="test">Hello &#x4F60;&#x597D; world</div>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_bug708874.css b/layout/inspector/tests/chrome/test_bug708874.css
new file mode 100644
index 0000000000..539c445380
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug708874.css
@@ -0,0 +1,33 @@
+#test-div {
+ color: rgb(0, 0, 0);
+ font-family: serif;
+ font-weight: 400;
+}
+
+#test-div:hover {
+ color: rgb(10, 0, 0);
+}
+
+#test-div:active {
+ font-family: Arial;
+}
+
+#test-div:focus {
+ font-weight: 800;
+}
+
+#test-button {
+ color: rgb(0, 0, 0);
+}
+
+#test-button:disabled {
+ color: rgb(40, 0, 0);
+}
+
+#test-link:visited {
+ color: rgb(20, 0, 0);
+}
+
+#test-link:link {
+ color: rgb(30, 0, 0);
+}
diff --git a/layout/inspector/tests/chrome/test_bug708874.xhtml b/layout/inspector/tests/chrome/test_bug708874.xhtml
new file mode 100644
index 0000000000..b240f4d768
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug708874.xhtml
@@ -0,0 +1,293 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_bug708874.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=708874
+-->
+<window title="Mozilla Bug 708874"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTests();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+
+/** Test for Bug 708874 - API for locking pseudo-class state of an element **/
+
+var DOMWindowUtils = window.windowUtils;
+
+var defaultColor = "rgb(0, 0, 0)";
+var disabledColor = "rgb(40, 0, 0)";
+
+function RunTests() {
+ testLockEnabled();
+ testLockDisabled();
+ testVisited();
+ testMultiple();
+ testInvalid();
+ testNotElement();
+}
+
+function testLockEnabled() {
+ var button = document.getElementById("test-button");
+
+ /* starting state is enabled */
+ button.removeAttribute("disabled");
+
+ is(InspectorUtils.hasPseudoClassLock(button, ":disabled"), false,
+ "doesn't have lock at start");
+
+ is(window.getComputedStyle(button).color, defaultColor,
+ "color is default color before locking on");
+
+ is(button.matches(":disabled"), false,
+ "doesn't match selector at start");
+
+ /* lock */
+ InspectorUtils.addPseudoClassLock(button, ":disabled");
+
+ is(InspectorUtils.hasPseudoClassLock(button, ":disabled"), true,
+ "hasPseudoClassLock is true after locking");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ ":disabled style applied after adding lock");
+
+ is(button.matches(":disabled"), true,
+ "matches selector after adding lock");
+
+ /* change state to disabled */
+ button.setAttribute("disabled", "disabled");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ ":disabled style still applied after really disabling");
+
+ is(button.matches(":disabled"), true,
+ "matches selector after adding lock");
+
+ /* remove lock */
+ InspectorUtils.removePseudoClassLock(button, ":disabled");
+
+ is(InspectorUtils.hasPseudoClassLock(button, ":disabled"), false,
+ "hasPseudoClassLock is false after removing on lock");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ ":disabled style still applied after removing lock");
+
+ is(button.matches(":disabled"), true,
+ "matches selector after removing lock");
+
+ /* change state to enabled */
+ button.removeAttribute("disabled");
+
+ is(window.getComputedStyle(button).color, defaultColor,
+ "back to default style after un-disabling");
+
+ is(button.matches(":disabled"), false,
+ "doesn't match selector after enabling");
+}
+
+
+function testLockDisabled() {
+ var button = document.getElementById("test-button");
+
+ /* starting state is disabled */
+ button.setAttribute("disabled", "disabled");
+
+ is(InspectorUtils.hasPseudoClassLock(button, ":disabled"), false,
+ "doesn't have lock at start");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ "color is :disabled color before locking");
+
+ is(button.matches(":disabled"), true,
+ "matches selector before locking");
+
+ /* lock */
+ InspectorUtils.addPseudoClassLock(button, ":disabled");
+
+ is(InspectorUtils.hasPseudoClassLock(button, ":disabled"), true,
+ "hasPseudoClassLock is true after locking");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ ":disabled style still applied after adding on lock");
+
+ is(button.matches(":disabled"), true,
+ "matches selector after locking");
+
+ /* change state to enabled */
+ button.removeAttribute("disabled");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ ":disabled style applied after enabling");
+
+ is(button.matches(":disabled"), true,
+ "matches selector after enabling with lock on");
+
+ /* remove lock */
+ InspectorUtils.removePseudoClassLock(button, ":disabled");
+
+ is(InspectorUtils.hasPseudoClassLock(button, ":disabled"), false,
+ "hasPseudoClassLock is false after removing on lock");
+
+ is(window.getComputedStyle(button).color, defaultColor,
+ "default style applied after removing lock");
+
+ is(button.matches(":disabled"), false,
+ "doesn't match selector after unlocking");
+
+ /* change state to disabled */
+ button.setAttribute("disabled", "disabled");
+
+ is(window.getComputedStyle(button).color, disabledColor,
+ ":disabled style applied after disabling after unlocking");
+
+ is(button.matches(":disabled"), true,
+ "matches selector again after disabling");
+}
+
+function testVisited() {
+ var link = document.getElementById("test-link");
+ var visitedColor = "rgb(20, 0, 0)";
+ var unvisitedColor = "rgb(30, 0, 0)";
+
+ /* lock visited */
+ InspectorUtils.addPseudoClassLock(link, ":visited");
+
+ is(InspectorUtils.hasPseudoClassLock(link, ":visited"), true,
+ "hasPseudoClassLock is true after adding lock");
+
+ var color = DOMWindowUtils.getVisitedDependentComputedStyle(link,
+ null, "color");
+ is(color, visitedColor, "color is :visited color after locking");
+
+ /* lock unvisited */
+ InspectorUtils.addPseudoClassLock(link, ":link");
+
+ is(InspectorUtils.hasPseudoClassLock(link, ":link"), true,
+ "hasPseudoClassLock is true after adding :link lock");
+
+ is(InspectorUtils.hasPseudoClassLock(link, ":visited"), false,
+ "hasPseudoClassLock is false for :visited after adding :link lock");
+
+ var color = DOMWindowUtils.getVisitedDependentComputedStyle(link,
+ null, "color");
+ is(color, unvisitedColor, "color is :link color after locking :link");
+
+ /* lock visited back on */
+ InspectorUtils.addPseudoClassLock(link, ":visited");
+
+ is(InspectorUtils.hasPseudoClassLock(link, ":visited"), true,
+ "hasPseudoClassLock is true after adding :visited lock");
+
+ is(InspectorUtils.hasPseudoClassLock(link, ":link"), false,
+ "hasPseudoClassLock is false for :link after adding :visited lock");
+
+ var color = DOMWindowUtils.getVisitedDependentComputedStyle(link,
+ null, "color");
+ is(color, visitedColor, "color is :visited color after locking back on");
+}
+
+function testMultiple() {
+ var div = document.getElementById("test-div");
+
+ var styles = {
+ ":hover": {
+ property: "color",
+ value: "rgb(10, 0, 0)",
+ defaultValue: "rgb(0, 0, 0)"
+ },
+ ":active": {
+ property: "font-family",
+ value: "Arial",
+ defaultValue: "serif"
+ },
+ ":focus": {
+ property: "font-weight",
+ value: "800",
+ defaultValue: "400"
+ }
+ };
+
+ for (var pseudo in styles) {
+ InspectorUtils.addPseudoClassLock(div, pseudo);
+ }
+
+ for (var pseudo in styles) {
+ is(InspectorUtils.hasPseudoClassLock(div, pseudo), true,
+ "hasPseudoClassLock is true after locking");
+
+ var style = styles[pseudo];
+ is(window.getComputedStyle(div).getPropertyValue(style.property),
+ style.value, "style for pseudo-class is applied after locking");
+
+ is(div.matches(pseudo), true,
+ "matches selector after locking");
+ }
+
+ InspectorUtils.clearPseudoClassLocks(div);
+
+ for (var pseudo in styles) {
+ is(InspectorUtils.hasPseudoClassLock(div, pseudo), false,
+ "hasPseudoClassLock is false after clearing");
+
+ is(window.getComputedStyle(div).getPropertyValue(style.property),
+ style.defaultValue, "style is back to default after clearing");
+
+ is(div.matches(pseudo), false,
+ "doesn't match selector after unlocking");
+ }
+}
+
+function testInvalid() {
+ var div = document.getElementById("test-div");
+ var pseudos = ["not a valid pseudo-class", ":ny-link", ":first-child"];
+
+ for (var i = 0; i < pseudos.length; i++) {
+ var pseudo = pseudos[i];
+
+ // basically make sure these don't crash the browser.
+ InspectorUtils.addPseudoClassLock(div, pseudo);
+
+ is(InspectorUtils.hasPseudoClassLock(div, pseudo), false);
+
+ InspectorUtils.removePseudoClassLock(div, pseudo);
+ }
+}
+
+function testNotElement() {
+ for (var value of [null, undefined, {}]) {
+ SimpleTest.doesThrow(() => InspectorUtils.hasPseudoClassLock(value, ":hover"),
+ "hasPseudoClassLock should throw for " + value);
+ SimpleTest.doesThrow(() => InspectorUtils.addPseudoClassLock(value, ":hover"),
+ "addPseudoClassLock should throw for " + value);
+ SimpleTest.doesThrow(() => InspectorUtils.removePseudoClassLock(value, ":hover"),
+ "removePseudoClassLock should throw for " + value);
+ SimpleTest.doesThrow(() => InspectorUtils.clearPseudoClassLocks(value),
+ "clearPseudoClassLocks should throw for " + value);
+ }
+}
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=708874"
+ target="_blank">Mozilla Bug 708874</a>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=708874">
+ Mozilla Bug 708874 - API for locking pseudo-class state of an element
+ </a>
+
+ <a id="test-link" href="http://notavisitedwebsite.com">
+ test link
+ </a>
+
+ <div id="test-div">
+ test div
+ </div>
+
+ <button id="test-button">
+ test button
+ </button>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_bug727834.css b/layout/inspector/tests/chrome/test_bug727834.css
new file mode 100644
index 0000000000..f21f7a54c2
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug727834.css
@@ -0,0 +1,7 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+body {
+ padding-top: 100px;
+}
diff --git a/layout/inspector/tests/chrome/test_bug727834.xhtml b/layout/inspector/tests/chrome/test_bug727834.xhtml
new file mode 100644
index 0000000000..03c72c4cff
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_bug727834.xhtml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!--
+vim: set ts=2 et sw=2 tw=80:
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_bug727834.css"?>
+<window title="Mozilla Bug 727834"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTests();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"><![CDATA[
+/** Test for Bug 727834 - Add an API to (re)parse a style sheet in place **/
+
+function RunTests() {
+ SimpleTest.waitForExplicitFinish();
+
+ let body = document.querySelector("body");
+ let testSheet = document.styleSheets[2];
+ let rule = document.styleSheets[2].cssRules[0];
+
+ is(testSheet.cssRules.length, 1,
+ "style sheet has 1 rule");
+ is(rule.style.paddingTop, "100px",
+ "original first rule has padding-top 100px");
+ is(window.getComputedStyle(body).paddingTop, "100px",
+ "original first rule applies");
+
+ InspectorUtils.parseStyleSheet(testSheet,
+ "@import url(test_bug727834.css); body{background: red;}");
+
+ is(testSheet.cssRules.length, 2,
+ "style sheet now has 2 rules");
+ is(window.getComputedStyle(body).backgroundColor, "rgb(255, 0, 0)",
+ "background is now red");
+
+ let exceptionName;
+ try {
+ rule.style.paddingLeft = "100px";
+ } catch (ex) {
+ exceptionName = ex.name;
+ } finally {
+ is(exceptionName, "NS_ERROR_NOT_AVAILABLE",
+ "original rule is not available for modification anymore");
+ }
+ is(window.getComputedStyle(body).paddingLeft, "0px",
+ "original rule does not apply to document");
+
+ rule = testSheet.cssRules[0];
+
+ is(rule.parentStyleSheet, testSheet,
+ "rule's parent style sheet is not null");
+
+ InspectorUtils.parseStyleSheet(testSheet,
+ "body{background: lime;}");
+
+ is(testSheet.cssRules.length, 1,
+ "style sheet now has 1 rule");
+ is(window.getComputedStyle(body).backgroundColor, "rgb(0, 255, 0)",
+ "background is now lime");
+ is(rule.parentStyleSheet, null,
+ "detached rule's parent style sheet is null");
+
+ SimpleTest.executeSoon(function () {
+ InspectorUtils.parseStyleSheet(testSheet,
+ "@import url(test_bug727834.css); body{background: blue;}");
+
+ is(testSheet.cssRules.length, 2,
+ "style sheet now has 2 rules");
+ is(window.getComputedStyle(body).backgroundColor, "rgb(0, 0, 255)",
+ "background is now blue");
+ is(testSheet.cssRules[0].parentStyleSheet, testSheet,
+ "parent style sheet is the test sheet");
+
+ SimpleTest.finish();
+ });
+}
+ ]]></script>
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=727834">
+ Mozilla Bug 727834 - Add an API to (re)parse a style sheet in place
+ </a>
+ </body>
+</window>
diff --git a/layout/inspector/tests/chrome/test_fontFaceGeneric.xhtml b/layout/inspector/tests/chrome/test_fontFaceGeneric.xhtml
new file mode 100644
index 0000000000..178036879d
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFaceGeneric.xhtml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="CSSGeneric attribute of InspectorFontFace"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function RunTest() {
+ var rng = document.createRange();
+ var elem, fonts;
+
+ elem = document.getElementById("test1");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "one font found");
+ is(fonts[0].CSSGeneric, "serif", "font " + fonts[0].name + " was specified as CSS generic");
+ var serifFamily = fonts[0].CSSFamilyName;
+
+ elem = document.getElementById("test2");
+ elem.style.fontFamily = serifFamily + ", serif";
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "one font found");
+ is(fonts[0].CSSFamilyName, serifFamily, "used the expected family (" + serifFamily + ")");
+ is(fonts[0].CSSGeneric, "", "font " + fonts[0].name + " was specified by name");
+
+ elem = document.getElementById("test3");
+ elem.getElementsByTagName("span")[0].style.fontFamily = serifFamily + ", serif";
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 2, "two fonts found");
+ var checked = 0;
+ fonts.forEach(function(f) {
+ if (f.CSSFamilyName == serifFamily) {
+ is(f.CSSGeneric, "", "serif font " + f.name + " was specified by name");
+ checked++;
+ } else {
+ is(f.CSSGeneric, "monospace", "monospace font " + f.name + " was specified as generic");
+ checked++;
+ }
+ });
+ is(checked, 2, "two fonts checked");
+
+ SimpleTest.finish();
+}
+ ]]>
+ </script>
+
+ <!-- html:body contains elements the test will inspect -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <div id="test1" style="font-family: serif">test one</div>
+ <div id="test2" style="font-family: monospace">test two</div>
+ <div id="test3" style="font-family: monospace">test <span>three</span></div>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_fontFaceRanges.css b/layout/inspector/tests/chrome/test_fontFaceRanges.css
new file mode 100644
index 0000000000..637c407980
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFaceRanges.css
@@ -0,0 +1,17 @@
+@font-face {
+ font-family: capitals;
+ src: url(GentiumPlus-R.woff) format("woff");
+ unicode-range: U+0041-005A;
+}
+@font-face {
+ font-family: lowercase;
+ src: url(GentiumPlus-R.woff) format("woff");
+ unicode-range: U+0061-007A;
+}
+@font-face {
+ font-family: gentium;
+ src: url(GentiumPlus-R.woff) format("woff");
+}
+.gentium {
+ font-family: capitals, lowercase, gentium, sans-serif;
+}
diff --git a/layout/inspector/tests/chrome/test_fontFaceRanges.xhtml b/layout/inspector/tests/chrome/test_fontFaceRanges.xhtml
new file mode 100644
index 0000000000..6a29095c25
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFaceRanges.xhtml
@@ -0,0 +1,257 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_fontFaceRanges.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1435989
+-->
+<window title="Mozilla Bug 1435989"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 1435989 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function expectRanges(f, ranges) {
+ var r = f.ranges;
+ is(r.length, ranges.length, "number of ranges");
+ for (var i = 0; i < Math.min(r.length, ranges.length); i++) {
+ is(r[i].toString(), ranges[i], "text of range");
+ }
+}
+
+function RunTest() {
+ var rng = document.createRange();
+ var elem, fonts;
+ var familyNames = new Map();
+
+ // Test element contains Latin & Chinese
+ elem = document.getElementById("test1");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+ is(fonts.length, 4, "number of font faces");
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ expectRanges(f, ["H", "W"]);
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["ello", "orld"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, [" ", " ", " ", "!"]);
+ break;
+ default:
+ // We'll get a platform-dependent font for the Chinese characters,
+ // so we don't attempt to check its name.
+ expectRanges(f, ["\u4F60", "\u597D"]);
+ break;
+ }
+ }
+ is(familyNames.size, 4, "found all expected families");
+
+ // Test element #test1 again, but limit maxRanges to 1 per face
+ elem = document.getElementById("test1");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 1);
+ is(fonts.length, 4, "number of font faces");
+ familyNames.clear();
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ expectRanges(f, ["H"]);
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["ello"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, [" "]);
+ break;
+ default:
+ // We'll get a platform-dependent font for the Chinese characters,
+ // so we don't attempt to check its name.
+ expectRanges(f, ["\u4F60"]);
+ break;
+ }
+ }
+ is(familyNames.size, 4, "found all expected families");
+
+ // Test element contains Latin & Arabic (for bidi)
+ elem = document.getElementById("test2");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+ is(fonts.length, 4, "number of font faces");
+ familyNames.clear();
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ expectRanges(f, ["H", "W"]);
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["ello", "orld"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, [" ", " ", "!"]);
+ break;
+ default:
+ // We'll get a platform-dependent font for the Arabic characters,
+ // so we don't attempt to check its name.
+ expectRanges(f, ["العربي"]);
+ break;
+ }
+ }
+ is(familyNames.size, 4, "found all expected families");
+
+ // Test element wrapped across multiple lines, including soft hyphens,
+ // one of which will be used as a line-break.
+ elem = document.getElementById("test3");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+ is(fonts.length, 3, "number of font faces");
+ familyNames.clear();
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ expectRanges(f, ["H", "W"]);
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["ello", "mul\u00ADti\u00ADline", "orld"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, [" ", " ", "!"]);
+ break;
+ default:
+ // There shouldn't be any other font used
+ ok(false, "unexpected font: " + f.CSSFamilyName);
+ break;
+ }
+ }
+ is(familyNames.size, 3, "found all expected families");
+
+ // Test where textrun should continue across inline element boundaries
+ // (but we expect to get separate Range objects).
+ elem = document.getElementById("test4");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+ is(fonts.length, 3, "number of font faces");
+ familyNames.clear();
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ expectRanges(f, ["H"]);
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["ello", "cruel", "world"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, ["!"]);
+ break;
+ default:
+ // There shouldn't be any other font used
+ ok(false, "unexpected font: " + f.CSSFamilyName);
+ break;
+ }
+ }
+ is(familyNames.size, 3, "found all expected families");
+
+ // Testcase involving collapsed whitespace, to test mapping from textrun
+ // offsets back to DOM content offsets.
+ elem = document.getElementById("test5");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+ is(fonts.length, 3, "number of font faces");
+ familyNames.clear();
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ expectRanges(f, ["H"]);
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["ello", "linked", "world"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, ["\n ", "\n ", "\n ", "!\n "]);
+ break;
+ default:
+ // There shouldn't be any other font used
+ ok(false, "unexpected font: " + f.CSSFamilyName);
+ break;
+ }
+ }
+ is(familyNames.size, 3, "found all expected families");
+
+ // Test that fonts used in non-visible elements are not reported,
+ // nor non-visible ranges for fonts that are also used in visible content.
+ elem = document.getElementById("test6");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+ is(fonts.length, 2, "number of font faces");
+ familyNames.clear();
+ for (var i = 0; i < fonts.length; i++) {
+ var f = fonts[i];
+ familyNames.set(f.CSSFamilyName, true);
+ switch (true) {
+ case f.CSSFamilyName == "capitals":
+ ok(false, "font used in hidden element should not be reported");
+ break;
+ case f.CSSFamilyName == "lowercase":
+ expectRanges(f, ["hello", "visible", "world"]);
+ break;
+ case f.CSSFamilyName == "gentium":
+ expectRanges(f, ["\n ", "\n ", "\n ", "!"]);
+ break;
+ default:
+ // There shouldn't be any other font used
+ ok(false, "unexpected font: " + f.CSSFamilyName);
+ break;
+ }
+ }
+ is(familyNames.size, 2, "found all expected families");
+
+ SimpleTest.finish();
+}
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1435989"
+ target="_blank">Mozilla Bug 1435989</a>
+ <!-- We use @font-face with unicode-range to force different font faces to be used
+ for uppercase, lowercase, and non-letter Latin characters; and then the Chinese
+ and Arabic characters will result in font fallback being applied. -->
+ <div class="gentium" id="test1">Hello &#x4F60; <b>World</b> &#x597D;!</div>
+ <div class="gentium" id="test2">Hello العربي World!</div>
+ <div class="gentium" id="test3" style="width:3em">Hello mul&#xAD;ti&#xAD;line World!</div>
+ <div class="gentium" id="test4"><span>Hello</span><span>cruel</span>world!</div>
+ <div class="gentium" id="test5">
+ Hello
+ <!-- comment -->
+ <a href="#foo">linked</a>
+ <!-- comment -->
+ world!
+ </div>
+ <div class="gentium" id="test6">
+ hello
+ <a href="#foo" style="visibility:hidden">Non-Visible
+ <span style="visibility:visible">visible</span></a>
+ world!</div>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_fontFeaturesAPI.css b/layout/inspector/tests/chrome/test_fontFeaturesAPI.css
new file mode 100644
index 0000000000..aa99e06f9b
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFeaturesAPI.css
@@ -0,0 +1,8 @@
+@font-face {
+ font-family: dejavu;
+ src: url(DejaVuSans.ttf);
+}
+
+#test1 {
+ font-family: dejavu, serif;
+}
diff --git a/layout/inspector/tests/chrome/test_fontFeaturesAPI.xhtml b/layout/inspector/tests/chrome/test_fontFeaturesAPI.xhtml
new file mode 100644
index 0000000000..631794bbb6
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFeaturesAPI.xhtml
@@ -0,0 +1,238 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_fontFeaturesAPI.css"?>
+<window title="Test for font variation axis API"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+// Expected result (sorted) from the DejaVu Sans font we're testing with:
+var dejaVuFeatures = [
+ ["aalt", "grek", "dflt"],
+ ["aalt", "hebr", "dflt"],
+ ["aalt", "latn", "ISM "],
+ ["aalt", "latn", "KSM "],
+ ["aalt", "latn", "LSM "],
+ ["aalt", "latn", "MOL "],
+ ["aalt", "latn", "NSM "],
+ ["aalt", "latn", "ROM "],
+ ["aalt", "latn", "SKS "],
+ ["aalt", "latn", "SSM "],
+ ["aalt", "latn", "dflt"],
+ ["ccmp", "DFLT", "dflt"],
+ ["ccmp", "arab", "KUR "],
+ ["ccmp", "arab", "SND "],
+ ["ccmp", "arab", "URD "],
+ ["ccmp", "arab", "dflt"],
+ ["ccmp", "armn", "dflt"],
+ ["ccmp", "brai", "dflt"],
+ ["ccmp", "cans", "dflt"],
+ ["ccmp", "cher", "dflt"],
+ ["ccmp", "cyrl", "MKD "],
+ ["ccmp", "cyrl", "SRB "],
+ ["ccmp", "cyrl", "dflt"],
+ ["ccmp", "geor", "dflt"],
+ ["ccmp", "grek", "dflt"],
+ ["ccmp", "hani", "dflt"],
+ ["ccmp", "hebr", "dflt"],
+ ["ccmp", "kana", "dflt"],
+ ["ccmp", "lao ", "dflt"],
+ ["ccmp", "latn", "ISM "],
+ ["ccmp", "latn", "KSM "],
+ ["ccmp", "latn", "LSM "],
+ ["ccmp", "latn", "MOL "],
+ ["ccmp", "latn", "NSM "],
+ ["ccmp", "latn", "ROM "],
+ ["ccmp", "latn", "SKS "],
+ ["ccmp", "latn", "SSM "],
+ ["ccmp", "latn", "dflt"],
+ ["ccmp", "math", "dflt"],
+ ["ccmp", "nko ", "dflt"],
+ ["ccmp", "ogam", "dflt"],
+ ["ccmp", "runr", "dflt"],
+ ["ccmp", "tfng", "dflt"],
+ ["ccmp", "thai", "dflt"],
+ ["dlig", "DFLT", "dflt"],
+ ["dlig", "armn", "dflt"],
+ ["dlig", "latn", "ISM "],
+ ["dlig", "latn", "KSM "],
+ ["dlig", "latn", "LSM "],
+ ["dlig", "latn", "MOL "],
+ ["dlig", "latn", "NSM "],
+ ["dlig", "latn", "ROM "],
+ ["dlig", "latn", "SKS "],
+ ["dlig", "latn", "SSM "],
+ ["dlig", "latn", "dflt"],
+ ["fina", "arab", "KUR "],
+ ["fina", "arab", "SND "],
+ ["fina", "arab", "URD "],
+ ["fina", "arab", "dflt"],
+ ["fina", "nko ", "dflt"],
+ ["hlig", "hebr", "dflt"],
+ ["hlig", "latn", "ISM "],
+ ["hlig", "latn", "KSM "],
+ ["hlig", "latn", "LSM "],
+ ["hlig", "latn", "MOL "],
+ ["hlig", "latn", "NSM "],
+ ["hlig", "latn", "ROM "],
+ ["hlig", "latn", "SKS "],
+ ["hlig", "latn", "SSM "],
+ ["hlig", "latn", "dflt"],
+ ["init", "arab", "KUR "],
+ ["init", "arab", "SND "],
+ ["init", "arab", "URD "],
+ ["init", "arab", "dflt"],
+ ["init", "nko ", "dflt"],
+ ["kern", "DFLT", "dflt"],
+ ["kern", "arab", "KUR "],
+ ["kern", "arab", "SND "],
+ ["kern", "arab", "URD "],
+ ["kern", "arab", "dflt"],
+ ["kern", "armn", "dflt"],
+ ["kern", "brai", "dflt"],
+ ["kern", "cans", "dflt"],
+ ["kern", "cher", "dflt"],
+ ["kern", "cyrl", "MKD "],
+ ["kern", "cyrl", "SRB "],
+ ["kern", "cyrl", "dflt"],
+ ["kern", "geor", "dflt"],
+ ["kern", "grek", "dflt"],
+ ["kern", "hani", "dflt"],
+ ["kern", "hebr", "dflt"],
+ ["kern", "kana", "dflt"],
+ ["kern", "lao ", "dflt"],
+ ["kern", "latn", "ISM "],
+ ["kern", "latn", "KSM "],
+ ["kern", "latn", "LSM "],
+ ["kern", "latn", "MOL "],
+ ["kern", "latn", "NSM "],
+ ["kern", "latn", "ROM "],
+ ["kern", "latn", "SKS "],
+ ["kern", "latn", "SSM "],
+ ["kern", "latn", "dflt"],
+ ["kern", "math", "dflt"],
+ ["kern", "nko ", "dflt"],
+ ["kern", "ogam", "dflt"],
+ ["kern", "runr", "dflt"],
+ ["kern", "tfng", "dflt"],
+ ["kern", "thai", "dflt"],
+ ["liga", "arab", "KUR "],
+ ["liga", "arab", "SND "],
+ ["liga", "arab", "URD "],
+ ["liga", "arab", "dflt"],
+ ["liga", "latn", "ISM "],
+ ["liga", "latn", "KSM "],
+ ["liga", "latn", "LSM "],
+ ["liga", "latn", "MOL "],
+ ["liga", "latn", "NSM "],
+ ["liga", "latn", "ROM "],
+ ["liga", "latn", "SKS "],
+ ["liga", "latn", "SSM "],
+ ["liga", "latn", "dflt"],
+ ["locl", "cyrl", "MKD "],
+ ["locl", "cyrl", "SRB "],
+ ["locl", "latn", "ISM "],
+ ["locl", "latn", "KSM "],
+ ["locl", "latn", "LSM "],
+ ["locl", "latn", "MOL "],
+ ["locl", "latn", "NSM "],
+ ["locl", "latn", "ROM "],
+ ["locl", "latn", "SKS "],
+ ["locl", "latn", "SSM "],
+ ["mark", "arab", "KUR "],
+ ["mark", "arab", "SND "],
+ ["mark", "arab", "URD "],
+ ["mark", "arab", "dflt"],
+ ["mark", "cyrl", "MKD "],
+ ["mark", "cyrl", "SRB "],
+ ["mark", "cyrl", "dflt"],
+ ["mark", "grek", "dflt"],
+ ["mark", "hebr", "dflt"],
+ ["mark", "lao ", "dflt"],
+ ["mark", "latn", "ISM "],
+ ["mark", "latn", "KSM "],
+ ["mark", "latn", "LSM "],
+ ["mark", "latn", "MOL "],
+ ["mark", "latn", "NSM "],
+ ["mark", "latn", "ROM "],
+ ["mark", "latn", "SKS "],
+ ["mark", "latn", "SSM "],
+ ["mark", "latn", "dflt"],
+ ["mark", "nko ", "dflt"],
+ ["mark", "tfng", "dflt"],
+ ["medi", "arab", "KUR "],
+ ["medi", "arab", "SND "],
+ ["medi", "arab", "URD "],
+ ["medi", "arab", "dflt"],
+ ["medi", "nko ", "dflt"],
+ ["mkmk", "arab", "KUR "],
+ ["mkmk", "arab", "SND "],
+ ["mkmk", "arab", "URD "],
+ ["mkmk", "arab", "dflt"],
+ ["mkmk", "cyrl", "MKD "],
+ ["mkmk", "cyrl", "SRB "],
+ ["mkmk", "cyrl", "dflt"],
+ ["mkmk", "grek", "dflt"],
+ ["mkmk", "lao ", "dflt"],
+ ["mkmk", "latn", "ISM "],
+ ["mkmk", "latn", "KSM "],
+ ["mkmk", "latn", "LSM "],
+ ["mkmk", "latn", "MOL "],
+ ["mkmk", "latn", "NSM "],
+ ["mkmk", "latn", "ROM "],
+ ["mkmk", "latn", "SKS "],
+ ["mkmk", "latn", "SSM "],
+ ["mkmk", "latn", "dflt"],
+ ["rlig", "arab", "KUR "],
+ ["rlig", "arab", "SND "],
+ ["rlig", "arab", "URD "],
+ ["rlig", "arab", "dflt"],
+ ["salt", "grek", "dflt"],
+ ["salt", "hebr", "dflt"],
+ ["salt", "latn", "ISM "],
+ ["salt", "latn", "KSM "],
+ ["salt", "latn", "LSM "],
+ ["salt", "latn", "MOL "],
+ ["salt", "latn", "NSM "],
+ ["salt", "latn", "ROM "],
+ ["salt", "latn", "SKS "],
+ ["salt", "latn", "SSM "],
+ ["salt", "latn", "dflt"],
+];
+
+function RunTest() {
+ var rng = document.createRange();
+ var elem = document.getElementById("test1");
+ rng.selectNode(elem);
+ var fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts");
+ var font = fonts[0];
+ is(font.CSSFamilyName, "dejavu", "using correct test font");
+ var fontFeatures = font.getFeatures();
+ is(fontFeatures.length, dejaVuFeatures.length, "number of features");
+ var featureList = [];
+ fontFeatures.forEach(function(f) {
+ featureList.push([f.tag, f.script, f.languageSystem]);
+ });
+ is(featureList.sort().join(";"), dejaVuFeatures.join(";"), "list of features");
+
+ SimpleTest.finish();
+}
+
+ ]]>
+ </script>
+
+ <!-- html:body contains elements the test will inspect -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1280162"
+ target="_blank">Mozilla Bug 1280162</a>
+ <div id="test1">Hello world</div>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_fontVariationsAPI.css b/layout/inspector/tests/chrome/test_fontVariationsAPI.css
new file mode 100644
index 0000000000..c8453485fd
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontVariationsAPI.css
@@ -0,0 +1,31 @@
+@font-face {
+ font-family: franklin;
+ /*
+ * LibreFranklinGX-Roman.ttf encoded in base64.
+ * Copyright (c) 2015, Impallari Type (www.impallari.com)
+ * This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL
+ */
+ src: url(data:font/ttf;base64,AAEAAAAUAQAABABARFNJRwAAAAEAAa0oAAAACEdERUYv/jCiAADtAAAAAOxHUE9TO4kw1AAA7ewAACI0R1NVQoWXfisAARAgAAAIeE9TLzJjQJcHAAAByAAAAGBjbWFwqsY1JgAAC4AAAAhGY3Z0IAfVB6IAABa0AAAAHGZwZ21HpnNCAAATyAAAAd9mdmFyhbdpkgABGJgAAABsZ2FzcAAaACMAAOzwAAAAEGdseWbQ6qxxAAAbiAAAt+ZndmFymktfUQABGQQAAJQkaGVhZApMCoIAAAFMAAAANmhoZWEHNwYtAAABhAAAACRobXR429pmUQAAAigAAAlYbG9jYUSKGF8AABbQAAAEtm1heHAC1AUOAAABqAAAACBuYW1lwvihsgAA03AAAAivcG9zdHYtv3QAANwgAAAQzXByZXDIPxThAAAVqAAAAQkAAQAAAAEDlTAMHFRfDzz1AAED6AAAAADT9NX7AAAAANRK7nj+Kf0cBM4EegAAAAcAAgAAAAAAAAABAAADT/9bAO4FD/4p/3AEzgABAAAAAAAAAAAAAAAAAAACUgABAAACWgB2AAcAkAAFAAEAAgAeAAQAAABkA+gAAwABAAMCLgGQAAUAAAKKAlgAAABLAooCWAAAAV4AMgE+AAAAAAUAAAAAAAAAIAAABwAAAAAAAAAAAAAAAElNUEEAQAAA+wIDT/9bAO4EjwD2IAABkwAAAAACEgLmAAAAIAADAuAAcgDXAAAA3AAAArwANAK8ADUCvAA1ArwANAK8ADUCvAA0ArwANAK8ADQCvAA1ArwANAK8ADUCvAA0ArwANAK8ADQCvAA1ArwANQK8ADUCvAA1ArwANQK8ADUCvAA1ArwANQK8ADUCfQAmArwANQPDADMDwwA0AqwAcgK3AE4CtwBOArcATgK3AE4CtwBOArcATgK/AHICvwAjAr8AcgK/ACMCvwByAmQAcgJkAHICZAByAmQAcgJkAHICZAByAmQAcgJkAHICZAByAmQAcgJkAHICZAByAmQAcgJkAHICZAByAmQAcgJkAHICZAByAmQAcgJkAHICRwByAtoATgLaAE4C2gBOAtoATgLaAE4C1QByAsMABQLVAHIC1QByARIAcgESABIBEgALARIACgES/8kBEgA7ARIAcgESAG8BEgAVARIAOgESAAoBEgAOARIACwESAAkBjwAuAY8ALwKiAHICogByAksAcgJLABACSwByAksAcgJoAHICSwAhA28AcgLzAHIC8wByAvMAcgLzAHIC8wByAvMAcgLzAHIC9gBOAvYATgL2AE4C9gBOAvYATgL2AE4C9gBOAvYATgL2AE4C9gBOAvYATgL2AE4C9gBOAvYATgL2AE4C9gBOAvYATgL2AE4C9gBOAvYATgL2AE4C9gBOAvYATgL2AE4CxAAVAsQAFgL2AE4EYgBOAqIAcgKiAHIC9gBOAqcAcgKnAHICpwByAqcAcgKnAHICpwByAqcAcgJqADkCagA6AmoAOgJqADoCagA6AmoAOgJqADoC9QBOAm8ALwJvAC8CbwAvAm8ALwJvAC8CbwAvAr4AaAK+AGgCvgBoAr4AaAK+AGgCvgBoAr4AaAK+AGgCvgBoAr4AaAK+AGgCvgBoAr4AaAK+AGgCvgBoAr4AaAK+AGgCvgBoAr4AaAK+AGgCvgBoArIAMwP9ADYD/QA3A/0ANwP9ADcD/QA3ApYAMwJ+AC8CfgAwAn4AMAJ+ADACfgAwAn4AMAJ+ADACfgAwAlgANwJYADcCWAA3AlgANwJYADcCGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwAhoAMAIaADACGgAwA6IAMAOiADACUABbAhkAOAIZADgCGQA4AhkAOAIZADgCGQA4AlAAOQJWADgCggA5AloAOQJQADkCQQA4AkEAOAJBADgCQQA4AkEAOAJBADgCQQA4AkEAOAJBADgCQQA4AkEAOAJBADgCQQA4AkEAOAJBADgCQQA4AkEAOAJBADgCQQA4AkEAOAJBADEBbAAaAkYAIwJGACMCRgAjAkYAIwJGACMCQABbAkAAFAJAAFsCQABbAN4AVgDeAFsA3v/1AN7/7gDe/+0A3v+sAN4AHgDeAFYA3gBWAN7/+ADeAB0A3v/tAN7/8QDe/+8A3v/sAN7/0QDe/9EA3v/RAhoAWwIaAFsCGgBbAN4AWwDe//UBDwBbAN4AUAEmAFsA8gADA4EAWwJAAFsCQABbAkAAWwJAAFsCQABbAkIAWwJAAFsCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCVgA4AlYAOAJWADgCPwATAj8AEwJWADgD+wA4Ak4AWwJMAFsCUQA6AWQAWwFkAFEBZABJAWQATQFkAAgBZABUAWQASQH1ABwB9QAcAfUAHAH1ABwB9QAcAfUAHAH1ABwCRgAeAWYAGgFmABoBZgAaAWYAGgFmABoBZgAaAjsAUwI7AFMCOwBTAjsAUwI7AEkCOwBTAjsAUwI7AFMCOwBTAjsAUwI7AFMCOwBTAjsAUwI7AFMCOwBTAjsAUwI7AFMCOwBTAjsAUwI7AFMCOwBTAhIAHwMPACMDDwAkAw8AJAMPACQDDwAkAgMAHgH8AB8B/AAgAfwAIAH8ACAB/AAgAfwAIAH8ACAB/AAgAcoAJAHKACQBygAkAcoAJAHKACQCSgAaAkoAGgGWADABowA4ApIAHgKjAE8BtgA3AjsAOQJwADECbwA0AoEAPAKGAE8CSwAvApMASQKBAEEB5AA/AW8AKQHEADYB0wA7AeMANgHWAD8B2QA/AbAAQAHVAEEB2QA7AaAAHQErAAcBgAAUAY8AGQGgABUBkQAcAZQAHAFtAB8BkQAfAZQAGAGgAB0BKwAHAYAAFAGPABkBoAAVAZEAHAGUABwBbQAfAZEAHwGUABgB5AA/AW8AKQHEADYB0wA7AeMANgHWAD8B2QA/AbAAQAHVAEEB2QA7APX/cQObACUDrQAvA8QAPANcAC8DcgAtA7kAOQPSAC0D1QAwA4kAKQFHAB0BSAAPAKwALQEmAEsAtgA8ALsALQKWADwA6ABVAOgAVQJbAC0AtgA8AgL+KQH6AB4BcwBQAOsAUADSAC0BSAAPAfUARgELAAABCwAPAQwAagEMAA8BDABQAQwAEwNeAEYCHQBGAX0ARgF9AEYBpQAgAaQAMAD9ACAA/gAxAWQAMQFkADwBZAAwANIAPADSADAA0gAwANcAAAIuADgCWAA5AgEARwI+ACQB7AAtAjf/7wGV/44CXwAGAlUADAHsABECxQAGAhsAAAJVAAwD4AATAkIAEwIdAEYCHQBGAgUARgIdAEYCHQBGAlgARgILAEYCCwA8AhMARgITADwCHQBGAh4AQQIeAEECHQBGAkEARgKwADIBqgAJAv8ARwJ9ACgCcwAOAfAAGQJZABMCSwA3AjsAUwOCAEEFDwBBAbUAKANCADUC5AAoAhYAOAHdAC4DPgAiAiEAIgM+ACIDcwAdAdAAUgELAHIBCwByAcQAHAGUAAABlAAAAz4AIgRKAHIB3AA7A3YAJAAA/1UAAP/EAAD++QAA/v4AAP68AAD/sQAA/vEAAP7xAAD+8wAA/y4AAP7wAAD++gAA/o8AAP69AAD+8wAA/7UAAP/CAAD/tgAA/0UAAP9HAIEAHQEQAA4BFwAKARgACgDEAAoBGAAKALUACgBGAAoBEAAKAWEAHQEQAAoAwwAKANwACgEaAAoAAP5G/kf+TP5V/kr+Uv5c/l0AAAAAAAMAAAADAAAAHAABAAAAAAZOAAMAAQAAABwABAYyAAAAkgCAAAYAEgAAAA0ALwA5AH4BMQFIAX4BjwGSAaEBsAHrAhsCNwJZArwCxwLdAwQDDAMPAxEDGwMjAygDwB4NHiUeRR5bHmMebR6FHpMe+SAUIBogHiAiICYgMCA6IEQgcCB5IKEgpCCmIKkgrCC6IRMhFyEgISIhJiEuIVQhXiICIgYiDyISIhoiHiIrIkgiYCJlJcr7Av//AAAAAAANACAAMAA6AKABNAFKAY8BkgGgAa8B6gH6AjcCWQK8AsYC2AMAAwYDDwMRAxsDIwMmA8AeDB4kHkQeWh5iHmwegB6SHqAgEyAYIBwgICAmIDAgOSBEIHAgdCChIKMgpiCpIKsguSETIRYhICEiISYhLiFTIVsiAiIFIg8iESIaIh4iKyJIImAiZCXK+wH//wJZ//QAAAFgAAAAAAAAAAD/BgBnAAAAAAAAAAD+5P6n/4cAAAAAAAAAAP8t/yz/I/8c/xr9zwAAAAAAAAAAAAAAAAAAAAAAAAAA4dcAAAAA4azh6+Gx4X7hSOFI4VPhV+FX4VfhTAAA4RUAAOEO4QLg7eD94HHgbeAWAADgBgAA3/3f89/n38XfpwAA3FIGigABAAAAAACOAAAAqgEyAlQCfAAAAAAC4ALiAuQC5gAAAAAAAAMiAyQDLgM2AAAAAAAAAAAAAAAAAzYDOAM6AzwDPgNAA0IDTANOBAAAAAQABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/IAAAPyAAAAAAAAAAAAAAAAAAAD5gAAA+YAAAAAAAAAAAAAA94AAAAAAAAAAgHTAdkB1QH2AhoCHgHaAeIB4wHMAgIB0QHmAdYB3AHQAdsCCQIGAggB1wIdAAMAHgAfACUAKgA+AD8ARABIAFYAWABaAGAAYQBoAIQAhgCHAI4AlgCcALEAsgC3ALgAwAHgAc0B4QItAd0CSwDFAOAA4QDnAOwBAQECAQcBCwEaAR0BIAEmAScBLgFKAUwBTQFUAVwBYgF3AXgBfQF+AYYB3gImAd8CDgHyAdQB8wH/AfUCAQInAiACSQIhAY0B6AIPAecCIgJNAiUCDAG6AbsCRAIZAh8BzgJHAbkBjgHpAcYBwwHHAdgAFAAEAAsAGwASABkAHAAiADgAKwAuADUAUABJAEsATQAmAGcAdABpAGsAggByAgQAgACjAJ0AnwChALkAhQFbANYAxgDNAN0A1ADbAN4A5AD6AO0A8AD3ARQBDQEPAREA6AEtAToBLwExAUgBOAIFAUYBaQFjAWUBZwF/AUsBgQAXANkABQDHABgA2gAgAOIAIwDlACQA5gAhAOMAJwDpACgA6gA7AP0ALADuADYA+AA8AP4ALQDvAEEBBABAAQMAQwEGAEIBBQBGAQkARQEIAFUBGQBTARcASgEOAFQBGABOAQwAVwEcAFkBHgEfAFsBIQBdASMAXAEiAF4BJABfASUAYgEoAGQBKgBjASkAZgEsAH4BRABqATAAfAFCAIMBSQCIAU4AigFQAIkBTwCPAVUAkgFYAJEBVwCQAVYAmQFfAJgBXgCXAV0AsAF2AK0BcwCeAWQArwF1AKsBcQCuAXQAtAF6ALoBgAC7AMEBhwDDAYkAwgGIAHYBPAClAWsAfwFFABoA3AAdAN8AgQFHABEA0wAWANgANAD2ADoA/ABMARAAUgEWAHEBNwB9AUMAiwFRAI0BUwCgAWYArAFyAJMBWQCaAWACSAJGAkUCSgJPAk4CUAJMAjECMgI1AjkCOgI3AjACLwI7AjgCMwI2ACkA6wBHAQoAZQErAIwBUgCUAVoAmwFhALYBfACzAXkAtQF7AMQBigATANUAFQDXAAwAzgAOANAADwDRABAA0gANAM8ABgDIAAgAygAJAMsACgDMAAcAyQA3APkAOQD7AD0A/wAvAPEAMQDzADIA9AAzAPUAMADyAFEBFQBPARMAcwE5AHUBOwBsATIAbgE0AG8BNQBwATYAbQEzAHcBPQB5AT8AegFAAHsBQQB4AT4AogFoAKQBagCmAWwAqAFuAKkBbwCqAXAApwFtAL0BgwC8AYIAvgGEAL8BhQHlAeQB7QHuAewCKQIqAc8B/gH8AiwCIwIQAhQCFgIDAgsCCgAGAfgAAAAJAPcAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB0wHZAdUB9gIaAh4B2gHiAeMBzAICAdEB5gHWAdwBkAGRAZIBkwGUAZUBlgGXAZgBmQHQAdsCCQIGAggB1wIdAAMAHgAfACUAKgA+AD8ARABIAFYAWABaAGAAYQBoAIQAhgCHAI4AlgCcALEAsgC3ALgAwAHgAc0B4QItAd0CSwDFAOAA4QDnAOwBAQECAQcBCwEaAR0BIAEmAScBLgFKAUwBTQFUAVwBYgF3AXgBfQF+AYYB3gImAd8CDgAAABIAGQAiACsAZwByAKEAxgDWAM0A1ADdANsA5ADtAPoA8AD3AQ0BFAEPAREBLQEvAToBMQE4AUgBYwFpAWUBZwIpAiUB8wH/AiABzwIfAVsCIgIhAiQCRAJJAgcAHACAAhECDAILAgoCAQIZAhgCFgIVAY8CEgGNAY4AAADeAUYB2AHUAg8CFwH5Ag0CFAHoAekB0gHyABQAGwCCAIMBSQHlAeQB7QHuAe8B8AIFAhwBgQC7AcIB+AHqAesBiwGMAioBzgHxAewCGwALAC4ABAA1ADgASQBLAE0AUABpAGsAAAB0AJ0AnwCjAQwCSAJQAk0CRQJKAk8CRwJMAk4CRgAAsAAsQA4FBgcNBgkUDhMLEggREEOwARVGsAlDRmFkQkNFQkNFQkNFQkNGsAxDRmFksBJDYWlCQ0awEENGYWSwFENhaUJDsEBQebEGQEKxBQdDsEBQebEHQEKzEAUFEkOwE0NgsBRDYLAGQ2CwB0NgsCBhQkOwEUNSsAdDsEZSWnmzBQUHB0OwQGFCQ7BAYUKxEAVDsBFDUrAGQ7BGUlp5swUFBgZDsEBhQkOwQGFCsQkFQ7ARQ1KwEkOwRlJaebESEkOwQGFCsQgFQ7ARQ7BAYVB5sgZABkNgQrMNDwwKQ7ASQ7IBAQlDEBQTOkOwBkOwCkMQOkOwFENlsBBDEDpDsAdDZbAPQxA6LbABLLcBAQAAAAAAAENwRbAAFUgTL0OwARUzQ7ABEy8tsAIstAoICAQFQ0VCS0JDsBBQebEEBEOwCUNgQkAKEQgDBQUBBQUHBENpQkOwB0NEQ2BCQ0VCQ7AKQ1J5sgYGB0OwA0OwBENhamBCHLEGB0NCsAVDsAZDRC2wAyxAExEGBgACAgECAgQKCggJAAAFAQJDRUJDcEVCQ0VCQ7ABQ7AJQ2FqYEJDsARDRENgQkNFQktCQ7AHQ1J5sgYDBEOwAEOwAUNhamBCHLEDBENCsAJDsANDRC0AALYCCQYKKAUBQkJCK7YCCQYKKAUDQkJCK7YCCQwKKAUFQkJCK7YCCQAKKAUHQkJCK7YCCQAKKAUJQkJCK7VGAgIBAUBCiEJDsCNTsAJDsEBRWnmwCbgQALIDAyCIQkNUebEwAbgBAEIcsAm5BCBAALADuBhgiEJjsANDVHmxFAG4AUBCHLAHuQwgQABjsANDVHmwAbgBAEK0NUABAAxCQ1R5sS0AQ7ANUHmzBwQEAENFQkOwXVB5sgkEQEIcsgQKBENgaUK4/82zAAEAAEOwBENEQ2BCHLEsAUOwQFJ5sSQAQ7AJUHm4/9a3AAEAAAUFBQBDRUJDsAFDY2mwAUNiQkOwBUNEQ2BCHAAAAAAAA08DWQLmAvACEgIcAAD/9v9b/1EAAAAoAAAAAAA5ADkAOQB6AIYAkgESASIBogIxAskC1QNWA2YD5wR3BRAFHAUoBTQFQAVMBVgFZAVwBXwF6wX3BlkGZQawBu0G+QcFBxEHHQcpB14HpwezB7sHxwgGCBIIHggqCDYItQjFCUQJ0wprCncKgwqPCpsKpwqzCr8KywrXCuMLGAttC3kLhQuRC50L3gwyDD4MSgxlDHEMfQyJDJUMoQytDLkMxQzRDN0M6Qz1DQENLQ05DX4Nig2vDbsNxw3TDd8OGQ5jDp0OqQ61DsEOzQ8VDyEPUA9cD2gPdA/jD/MQYhDhEWkRdRGBEY0RmRGlEbESDhJmEsQTMROnE7MTvxPLE9cUKxQ3FEMUpxTgFSAVZxWxFb0VyRXVFeEV7RX5FkgWVBZgFmwWeBaEFpAW1RcDF0UXURddF2kXdReeF6oXthfCF84X2hfmF/IX/hgKGGAYsRkHGW0Z2xnnGfMZ/xoLGhcaIxpSGqAarBq4GsQa0BsZG1MbXxtrG3cbgxuPG5sbpxvgG+wb+BwEHBAcahx2HIIckhyiHLIcwhzSHN4c7hz+HQ4dHh0uHTodRh1SHV4dah12HYIdjh2aHaodth5AHkwekB7UHuAe7B74HwQfEB9aH7wfyCAmIDIgeCCEIJAgnCCoILggyCDYIOgg+CEEIRAhHCEoITQhQCFMIVghZCFwIbch9SJ5IoUikSM1I0EjeCPEI9Aj3CQLJCYkMiQ+JEokViRiJG4keiSGJJIkniSqJLokxiT7JR0lKSVtJXklvSXYJeQl8CX8JggmNSaNJsMmzybbJucm8yczJz8ncCd8J4gnlCekJ7QnxCfUJ+Qn8Cf8KAgoFCggKCwoiyjlKUQpsiopKjUqQSpNKlkqpCqwKrwrMSt8K8gsFCxFLFEsXSxpLHUsgSyNLN4s6iz2LQItDi0aLSYtlS3ULiUuMS49LkkuVS6KLpYuoi6uLrouxi7SLt4u6i72L1kvtzAbMI4xCjEWMSIxLjE6MUYxUjF/MdAx3DHoMfQyADJGMn4yijKWMqIyrjK6MsYy0jMLMxczIzMvMzszRzNTM6oz1jQSNEA0dzTANR01ZDW9Ng82PzacNu429zcANwk3EjcbNyQ3LTc2Nz83SDd2N6w39DhROJU46zk9OWw5yDoaOkg6fjrGOyM7Zzu9PA88PjyaPOw89Dz8PQQ9DD0UPRw9JD0sPTQ9PD1cPWw9fD2MPZw9rD28Pcw93D3sPkQ+YT54PpQ+vz7lPvU/Iz9RP68/x0APQFZAYkB+QLZA0kDtQTZBf0GmQc5B90IfQjpCVUJvQndCtkL1QxhDOkNGQ4tD0EP2RBxEQkRCRK1FSkXFRlxGxEcpR39HyUhbSLlJG0l+SelKYErDSvJLDUtFS4FLsEv1TB1MRUyBTL1NAE1mTZ1NyU4VTnlOwU8bT01Pjk/IT/tQTlCOUP5RmVHZUldS51MdU5JT/VRpVMdVOFVmVYBVrlYLVk9WulcDVw9XOlfMV/lYE1gzWFNYiFixWN5ZClk1WVtZk1msWdxaEVo8WldacVqYWsda8FsXWyBbKVsyWztbRFtNW1VbXltnW3BbeVuCW4tbmFulW7Jbv1vMW9lb5lvzW/MAAAADAHL/WwJuA08ADwAeACIAAAUUBiMhIiY1ETQ2MyEyFhUCNRE0JiMhIgYVERQWMyEVATcBAm4QEf5ADwwKCQHXCAo0CAf+iwcJCg0BYf5pNAGfiBANDA0DxAsMCwr8UxgDZggKCgn8mAsKFgOvG/w7AAACADQAAAKJAuYAHAAnAAAzIiY3ATY2MzMyFhcBFgYjIyInJyYmIyEiBwcGIxIWMyEyJwMmBgcDPAUDAQEJAgcHIQgJAQEGAgUFGAoDUQIEBP6+BwNTAQpvAgUBIwoDjwQHBJEFBALSBgUFBf0uBQUI3wQDB+AHARkDCgGGCgEK/nn//wA0AAACiQPIACIAAwAAAAcCMgHmANT//wA0AAACiQO6ACIAAwAAAAcCNwHqANQABAA0AAACiQR6ABwAJwBCAFIAADMiJjcBNjYzMzIWFwEWBiMjIicnJiYjISIHBwYjEhYzITInAyYGBwMBMhYHBgYjIiYnJjYzMzIWFx4CMzI2NzY2MzYWFxcWBgcHBiYnJyY2Nzc8BQMBAQkCBwchCAkBAQYCBQUYCgNRAgQE/r4HA1MBCm8CBQEjCgOPBAcEkQESBgMCCEU2ND8LAQEEIwQFAwUSHhYfJgsCBAMYBQEKAgMG4wUFAQUBAgLlBQQC0gYFBQX9LgUFCN8EAwfgBwEZAwoBhgoBCv55ApwEBR41MyECBgQDCBUPGxIDA8ACAiAFBQJKAgMEDwQFAVsA//8ANP9bAokDugAiAAMAAAAjAj8BggAAAAcCNwHqANQABAA0AAACiQR2ABwAJwBCAFIAADMiJjcBNjYzMzIWFwEWBiMjIicnJiYjISIHBwYjEhYzITInAyYGBwMBMhYHBgYjIiYnJjYzMzIWFx4CMzI2NzY2Myc2NhcXFhYHBwYGJycmJjc8BQMBAQkCBwchCAkBAQYCBQUYCgNRAgQE/r4HA1MBCm8CBQEjCgOPBAcEkQESBgMCCEU2ND8LAQEEIwQFAwUSHhYfJgsCBAPFAQUC5QICAQUBBQXjBgMCBQQC0gYFBQX9LgUFCN8EAwfgBwEZAwoBhgoBCv55ApwEBR41MyECBgQDCBUPGxIDA7gCAgFbAQUEDwQDAkoCBQUAAAQANAAAAokEeQAcACcAQgBfAAAzIiY3ATY2MzMyFhcBFgYjIyInJyYmIyEiBwcGIxIWMyEyJwMmBgcDATIWBwYGIyImJyY2MzMyFhceAjMyNjc2NjMmBgcGBhcXFjc2MzIWFRQGBwYUFxcWNzY2NTQmIzwFAwEBCQIHByEICQEBBgIFBRgKA1ECBAT+vgcDUwEKbwIFASMKA48EBwSRARIGAwIIRTY0PwsBAQQjBAUDBRIeFh8mCwIEA2clDwIBAQoCAx8TFBYXFAIBDgQCGx0nIAUEAtIGBQUF/S4FBQjfBAMH4AcBGQMKAYYKAQr+eQKcBAUeNTMhAgYEAwgVDxsSAwO/DQkBBAIXBAIUDxIMIBQCBAERAwIbKxUeIwAEADQAAAKJBGEAHAAnAEIAZQAAMyImNwE2NjMzMhYXARYGIyMiJycmJiMhIgcHBiMSFjMhMicDJgYHAwEyFgcGBiMiJicmNjMzMhYXHgIzMjY3NjYzNgYHBgYjIiYmIyIGBgcGJicnJjY3NjYzMhYWMzI2NzYWFxc8BQMBAQkCBwchCAkBAQYCBQUYCgNRAgQE/r4HA1MBCm8CBQEjCgOPBAcEkQESBgMCCEU2ND8LAQEEIwQFAwUSHhYfJgsCBAMqAgQOHRgWJyQRDhMQBwUEAgcBAQITHRQSKikSDxQPBQYCCAUEAtIGBQUF/S4FBQjfBAMH4AcBGQMKAYYKAQr+eQKcBAUeNTMhAgYEAwgVDxsSAwOGBAIJCgwMBgoEAgEFEAMEAQ0NDQwHCQMBBRL//wA0AAACiQO6ACIAAwAAAAcCNQHqANQABAA0AAACiQROABwAJwBDAFMAADMiJjcBNjYzMzIWFwEWBiMjIicnJiYjISIHBwYjEhYzITInAyYGBwMSJicnJiIHBwYGIyMiJjc3NjYzMzIWFxcWFCMjNhYXFxYGBwcGJicnJjY3NzwFAwEBCQIHByEICQEBBgIFBRgKA1ECBAT+vgcDUwEKbwIFASMKA48EBwSR5wgDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpHQUBCgIDBuMFBQEFAQIC5QUEAtIGBQUF/S4FBQjfBAMH4AcBGQMKAYYKAQr+eQI6AwMwBgYwAwMGBE8EBQUDUAUF9gICIAUFAkoCAwQPBAUBWwD//wA0/1sCiQO6ACIAAwAAACMCPwGCAAAABwI1AeoA1AAEADQAAAKJBEoAHAAnAEMAUwAAMyImNwE2NjMzMhYXARYGIyMiJycmJiMhIgcHBiMSFjMhMicDJgYHAxImJycmIgcHBgYjIyImNzc2NjMzMhYXFxYUIyMnNjYXFxYWBwcGBicnJiY3PAUDAQEJAgcHIQgJAQEGAgUFGAoDUQIEBP6+BwNTAQpvAgUBIwoDjwQHBJHnCAM7BwQIPgQHBCUFAQRjBAoGEgUIA2IFBinAAQUC5QICAQUBBQXjBgMCBQQC0gYFBQX9LgUFCN8EAwfgBwEZAwoBhgoBCv55AjoDAzAGBjADAwYETwQFBQNQBQXuAgIBWwEFBA8EAwJKAgUFAAAEADQAAAKJBEoAHAAnAEMAYAAAMyImNwE2NjMzMhYXARYGIyMiJycmJiMhIgcHBiMSFjMhMicDJgYHAxImJycmIgcHBgYjIyImNzc2NjMzMhYXFxYUIyM2BgcGBhcXFjc2MzIWFRQGBwYUFxcWNzY2NTQmIzwFAwEBCQIHByEICQEBBgIFBRgKA1ECBAT+vgcDUwEKbwIFASMKA48EBwSR5wgDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpFCUPAgEBCgIDHxMUFhcUAgEOBAIbHScgBQQC0gYFBQX9LgUFCN8EAwfgBwEZAwoBhgoBCv55AjoDAzAGBjADAwYETwQFBQNQBQXyDQkBBAIXBAIUDxIMIBQCBAERAwIbKxUeIwAEADQAAAKJBDUAHAAnAEMAZgAAMyImNwE2NjMzMhYXARYGIyMiJycmJiMhIgcHBiMSFjMhMicDJgYHAxImJycmIgcHBgYjIyImNzc2NjMzMhYXFxYUIyM2BgcGBiMiJiYjIgYGBwYmJycmNjc2NjMyFhYzMjY3NhYXFzwFAwEBCQIHByEICQEBBgIFBRgKA1ECBAT+vgcDUwEKbwIFASMKA48EBwSR5wgDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpLwIEDh0YFickEQ4TEAcFBAIHAQECEx0UEiopEg8UDwUGAggFBALSBgUFBf0uBQUI3wQDB+AHARkDCgGGCgEK/nkCOgMDMAYGMAMDBgRPBAUFA1AFBbwEAgkKDAwGCgQCAQUQAwQBDQ0NDAcJAwEFEv//ADQAAAKJA8QAIgADAAAABwI8Ad4A1P//ADQAAAKJA7oAIgADAAAABwIvAbgA1P//ADT/WwKJAuYAIgADAAAAAwI/AYIAAP//ADQAAAKJA8QAIgADAAAABwIxAe0A1P//ADQAAAKJA8cAIgADAAAABwI7An0A1P//ADQAAAKJA8IAIgADAAAABwI9AekA1P//ADQAAAKJA6YAIgADAAAABwI6AeYA1P//ADT/VwKiAuYAIgADAAAABwJCAqz/////ADQAAAKJA8QAIgADAAAABwI4AcwApQAFACYAAAJWA8QACwAXADQAPwBJAAAAFhUUBiMiJjU0NjMWNjU0JiMiBhUUFjMGNjMzMhYXExYGIyMiJycmJiMhIgcHBiMjIiY3ExInAyYGBwMGFjMhAjYzMzIWBwcjNwFoOjoqKjo7KRciIRkYIyQXHAcHIAgJAfMCBQUbCQNMAgQE/tgHA00DCBYEBQL3qwOEAwgEhQIBBQENfQgIFwgFAx8mEgOEOSkrOTsrKDieIxgaISEaFyQFBQUF/S4FBQjfBAMH4AcFBALS/jsKAYYKAQr+eQUDAqgGCAhJTP//ADQAAAKJA68AIgADAAAABwI5AesA1AACADMAAAOPAuYAOgBGAAAlMhYVFRQGIyEiJjU1NCMjIgcHBiMjIiY3ATY2MyEyFRUUBiMhIgYVERQWMyEyFRUUBiMhIgYVERQWMycyNRE0JgYHAwYWMwOHBQMDBP5wBQQF/ggEgQQIGQQDAgGdAwgHAZUHAwX+rgUEBAUBDQcDBf70BQQEBT8IBQYE5AMBBSgDBBoEAwQG3wUH4AcFBALSBgUHGgQDAwb+3QQDBxoEAwMG/tcGA+4JAYcGBQUH/nkFAwD//wAzAAADjwPIACIAHAAAAAcCMgMxANQAAwByAAACYQLmABcAJgA1AAAzIiY1ETQ2MzMyFhUUBgcGFBcWFhUUBiMmFjMzMjY1NCYjIyIGFREQFjMzMjY1NCYjIyIGFRF6BAQEBc19ikFCBARGT5GFqwMEn3lyeGWpBgUDBK1VdnNomwUEBAQC1AYEY1w/VhQBBwENWkliYy0FR1BVTwQF/tgBXgVEU09NBAT+3wAAAQBO//YCfgLwACcAABI2NjMyFhYXFgYjBwYmJyYmIyIGFRQWMzI2NzYXFxYHDgIjIiYmNU5Ghl08cUsGAQIEJAMDARFvTHqBg4BOZh4CBCIDARFLaDxgiEcB6K1bN1ozBAMEAQQER1evp6OtS08DAQoCBTdRLViqeP//AE7/9gJ+A8gAIgAfAAAABwIyAf8A1P//AE7/9gJ+A7oAIgAfAAAABwI2AgMA1P//AE7/NAJ+AvAAIgAfAAAAAwJBAdAAAP//AE7/9gJ+A7oAIgAfAAAABwI1AgMA1P//AE7/9gJ+A7oAIgAfAAAABwIwAZoA1AACAHIAAAJyAuYAEgAjAAASNjMyFjMeAhUUBgYjIyImNREXIgYVERQWMzMyNjY1NCYmI3IFBRJLFouqTlaximYFBDgGBAUGMoKbSEuZfwLhBQEBT558hKdQBQUC0h8HB/2CBQRBkXmBkDkAAwAjAAACcgLmAA4AIQAyAAAAFhUVFAYjISImNTU0MyECNjMyFjMeAhUUBgYjIyImNREXIgYVERQWMzMyNjY1NCYmIwFTAwYH/uQGBAcBJd0FBRJLFouqTlaximYFBDgGBAUGMoKbSEuZfwGLBAQXBgUFBRcJAVYFAQFPnnyEp1AFBQLSHwcH/YIFBEGReYGQOf//AHIAAAJyA7oAIgAlAAAABwI2AdwA1P//ACMAAAJyAuYAAgAmAAD//wBy/1sCcgLmACIAJQAAAAMCPwFwAAAAAQByAAACMALmAC0AABM0NjMhMhUVFAYjISIGFREUFjMhMhUVFAYjISIGFREUFjMhMhYVFRQGIyEiJjVyBAUBnwcDBf6QBQQEBQEhBwMF/uAFBAQFAX8FAwME/lIFBALcBgQHGgQDAwb+3QQDBxoEAwMG/tcGAwMEGgQDBAb//wByAAACMAPIACIAKgAAAAcCMgHhANT//wByAAACMAO6ACIAKgAAAAcCNwHlANT//wByAAACMAO6ACIAKgAAAAcCNgHlANT//wByAAACMAO6ACIAKgAAAAcCNQHlANQAAwByAAACMAROAC0ASQBZAAATNDYzITIVFRQGIyEiBhURFBYzITIVFRQGIyEiBhURFBYzITIWFRUUBiMhIiY1ACYnJyYiBwcGBiMjIiY3NzY2MzMyFhcXFhQjIzYWFxcWBgcHBiYnJyY2NzdyBAUBnwcDBf6QBQQEBQEhBwMF/uAFBAQFAX8FAwME/lIFBAE2CAM7BwQIPgQHBCUFAQRjBAoGEgUIA2IFBikdBQEKAgMG4wUFAQUBAgLlAtwGBAcaBAMDBv7dBAMHGgQDAwb+1wYDAwQaBAMEBgNOAwMwBgYwAwMGBE8EBQUDUAUF9gICIAUFAkoCAwQPBAUBW///AHL/WwIwA7oAIgAqAAAAIwI/AZAAAAAHAjUB5QDUAAMAcgAAAjAESgAtAEkAWQAAEzQ2MyEyFRUUBiMhIgYVERQWMyEyFRUUBiMhIgYVERQWMyEyFhUVFAYjISImNQAmJycmIgcHBgYjIyImNzc2NjMzMhYXFxYUIyMnNjYXFxYWBwcGBicnJiY3cgQFAZ8HAwX+kAUEBAUBIQcDBf7gBQQEBQF/BQMDBP5SBQQBNggDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpwAEFAuUCAgEFAQUF4wYDAgLcBgQHGgQDAwb+3QQDBxoEAwMG/tcGAwMEGgQDBAYDTgMDMAYGMAMDBgRPBAUFA1AFBe4CAgFbAQUEDwQDAkoCBQUAAwByAAACMARKAC0ASQBmAAATNDYzITIVFRQGIyEiBhURFBYzITIVFRQGIyEiBhURFBYzITIWFRUUBiMhIiY1ACYnJyYiBwcGBiMjIiY3NzY2MzMyFhcXFhQjIzYGBwYGFxcWNzYzMhYVFAYHBhQXFxY3NjY1NCYjcgQFAZ8HAwX+kAUEBAUBIQcDBf7gBQQEBQF/BQMDBP5SBQQBNggDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpFCUPAgEBCgIDHxMUFhcUAgEOBAIbHScgAtwGBAcaBAMDBv7dBAMHGgQDAwb+1wYDAwQaBAMEBgNOAwMwBgYwAwMGBE8EBQUDUAUF8g0JAQQCFwQCFA8SDCAUAgQBEQMCGysVHiMAAAMAcgAAAjAENQAtAEkAbAAAEzQ2MyEyFRUUBiMhIgYVERQWMyEyFRUUBiMhIgYVERQWMyEyFhUVFAYjISImNQAmJycmIgcHBgYjIyImNzc2NjMzMhYXFxYUIyM2BgcGBiMiJiYjIgYGBwYmJycmNjc2NjMyFhYzMjY3NhYXF3IEBQGfBwMF/pAFBAQFASEHAwX+4AUEBAUBfwUDAwT+UgUEATYIAzsHBAg+BAcEJQUBBGMECgYSBQgDYgUGKS8CBA4dGBYnJBEOExAHBQQCBwEBAhMdFBIqKRIPFA8FBgIIAtwGBAcaBAMDBv7dBAMHGgQDAwb+1wYDAwQaBAMEBgNOAwMwBgYwAwMGBE8EBQUDUAUFvAQCCQoMDAYKBAIBBRADBAENDQ0MBwkDAQUSAP//AHIAAAIwA8QAIgAqAAAABwI8AdkA1P//AHIAAAIwA7oAIgAqAAAABwIvAbMA1P//AHIAAAIwA7oAIgAqAAAABwIwAXwA1P//AHL/WwIwAuYAIgAqAAAAAwI/AX4AAP//AHIAAAIwA8QAIgAqAAAABwIxAegA1P//AHIAAAIwA8cAIgAqAAAABwI7AngA1P//AHIAAAIwA8IAIgAqAAAABwI9AeQA1P//AHIAAAIwA6YAIgAqAAAABwI6AeEA1P//AHL/VgJIAuYAIgAqAAAABwJCAlL//v//AHIAAAIwA68AIgAqAAAABwI5AeYA1AABAHIAAAIXAuYAJQAAEzQ2MyEyFRUUBiMhIgYVERQWMyEyFRUUBiMhIgYVERQGIyMiJjVyBAUBlQcDBf6aBQQEBQErBwMF/tYFBAMEHgUEAtwGBAcaBAMDBv7QBAMHGgQDAwb+ugQDBAYAAQBO//YChALwAD4AACQGBiMiJiY1NDY2MzIWFhcUBgcHBiYnLgIjIgYVFBYzMjY2NTU0IyMiJjU1NDMzMhYVERQGIyMiJjU1NCIHAkA/VzVchUZIhVs8ckoGAgMjAgQBCDpZNHiAfnpKaDQLvQQDBewDAwQGEQcGBQNOOCBZqnd5rVo1WTMDBAEGAQMELEkqsZ+msEVnM08IAwMXBgMD/pgGBQMGagUFAP//AE7/9gKEA7oAIgA/AAAABwI3AgIA1P//AE7/9gKEA7oAIgA/AAAABwI1AgIA1P//AE7/FAKEAvAAIgA/AAAAAwJAAaUAAP//AE7/9gKEA7oAIgA/AAAABwIwAZkA1AABAHIAAAJjAuYALgAAACYjISIGFREUBiMjIiY1ETQ2MzMyFhURFBYzITI1ETQ2MzMyFhURFAYjIyImNRECNQME/ngDAwUFGwUEBAUcBQQEBQGECAQEHQUEBAUbBQUBawMEAv6hBQQFBQLSBgQEBv7DBgMIAT8FBAQG/S4FBQQFAV4AAgAFAAACvgLmAA4APQAAABYVFRQGIyEiJjU1NDMhBiYjISIGFREUBiMjIiY1ETQ2MzMyFhURFBYzITI1ETQ2MzMyFhURFAYjIyImNRECuwMGB/1eBgQHAquDAwT+eAMDBQUbBQQEBRwFBAQFAYQIBAQdBQQEBRsFBQJqBAQVBgUFBRUJ/wMEAv6hBQQFBQLSBgQEBv7DBgMIAT8FBAQG/S4FBQQFAV7//wByAAACYwO6ACIARAAAAAcCNQH3ANT//wBy/1sCYwLmACIARAAAAAMCPwGPAAAAAQByAAAAoALmAA8AABI2MzMyFhURFAYjIyImNRFyBAUcBQQECBcHBALiBAQG/TAHBQQGAtL//wASAAABDwPIACIASAAAAAcCMgEUANT//wALAAABDwO6ACIASAAAAAcCNwEYANT//wAJAAABDgO6ACIASAAAAAcCNQEYANT////JAAAA7wPEACIASAAAAAcCPAEMANT//wA7AAAA3AO6ACIASAAAAAcCLwDmANT//wByAAAApQO6ACIASAAAAAcCMACvANT//wBv/1sAowLmACIASAAAAAMCPwCtAAD//wAUAAABEQPEACIASAAAAAcCMQEbANT//wA6AAAAxgPHACIASAAAAAcCOwGrANT//wAKAAABDgPCACIASAAAAAcCPQEXANT//wAOAAABCgOmACIASAAAAAcCOgEUANT//wAL/1cAugLmACIASAAAAAcCQgDE/////wAJAAABDwOvACIASAAAAAcCOQEZANQAAQAu//YBIQLmABsAACUUBiMiJicmJjc3NhceAjMyNjY1ETQ2MzMyFQEhPUYkPgkCAwEJAQcLGiEWISYQBAQeCJBLTxIHAQUDHwcDBQwIFTQvAkMFBAoA//8ALv/2AYwDugAiAFYAAAAHAjUBlgDUAAEAcgAAAnEC5gAtAAAlFgYjIyInASYiBwcGBhUVFAYjIyImNRE0NjMzMhURFBY3ATY2MzMyFgcBBhQXAm8CAgMnCAP+/gMFBIQEBAUFGgUFBAQeCAMEAX8CBQQmBAID/v0BAQcDBAUBlAQEkgQHBu0FBAQFAtUEBAj+ZQQBBAGgAgIFA/7lAQMC//8Acv8UAnEC5gAiAFgAAAADAkABgQAAAAEAcgAAAhwC5gAXAAATNDYzMzIWFREUFjMhMhYVFRQGIyEiJjVyBAUcBQQEBQFrBQMDBP5mBQQC3AYEBAb9VQYDAwQaBAMFBQD//wAQAAACHAPIACIAWgAAAAcCMgESANT//wByAAACHALmACIAWgAAAAMCNAHUAAD//wBy/xQCHALmACIAWgAAAAMCQAFbAAD//wByAAACHALmACIAWgAAAAcBzgFzABcAAgAhAAACHALmAA0AJQAAABUVFAcFBiY1NTQ2NyUDNDYzMzIWFREUFjMhMhYVFRQGIyEiJjUBRwb+5wQDAwIBGs4EBRwFBAQFAWsFAwME/mYFBAG2BhgGAkkBAgQYAwUBSAEnBgQEBv1VBgMDBBoEAwUFAAABAHIAAAL9AuYAMQAAMyImNRE0NjMzMhYXARY2NwE2MzMyFhURFAYjIyImNRE0BgcDBgYjIyInAyYmFREUBiN7BQQEBRoGBQIBCwQIBAEPAwgdBQQEBRsFBQIB+QIHBxsPBPQBAgUFBQUC0gYEAwX9YgoBCgKeBwQG/S4FBQQFAmoDAQL9mAYFCgJkAgED/ZsFBAAAAQByAAACgQLmACUAAAAzMzIWFREUBiMjIiYnASYGFREUBiMjIjURNDYzMzIWFwEWNjURAlUIGwUEBAUPBgoD/lQFBwUFGQkEBRIGBgMBqwUJAuYEBv0uBQUBBQJ3BwEG/YwFBAoC0gYEAwT9igcEBQJyAP//AHIAAAKBA8gAIgBhAAAABwIyAhIA1P//AHIAAAKBA7oAIgBhAAAABwI2AhYA1P//AHL/FAKBAuYAIgBhAAAAAwJAAZEAAP//AHIAAAKBA7oAIgBhAAAABwIwAa0A1AABAHL/UQKBAuYAMAAAATIWFREUBiMiJicmJjU3NhcWMzI2NTUBJgYVERQGIyMiNRE0NjMzMhYXARY2NRE0MwJ4BQQ8QxwuBwICAwEHIyMrKv5UBQQFBRkJBAUVBQYDAaoFCQgC5gQG/PY+QwoFAQUDGQcDDSszNQJ0BwEG/YkFBAoC0gYEAwT9iQcFBQJyCf//AHIAAAKBA68AIgBhAAAABwI5AhcA1AACAE7/9gKoAvAADwAdAAAkBgYjIiYmNTQ2NjMyFhYVBBYWMzI2NjU0JiMiBhUCqEiHXl+HR0iIXl6GSP3WOnJTU285gHx9gfeqV1apenysWVise3CWS0qWcaqrq6r//wBO//YCqAPIACIAaAAAAAcCMgIFANT//wBO//YCqAO6ACIAaAAAAAcCNwIJANT//wBO//YCqAO6ACIAaAAAAAcCNQIJANQABABO//YCqAROAA8AHQA5AEkAACQGBiMiJiY1NDY2MzIWFhUEFhYzMjY2NTQmIyIGFQAmJycmIgcHBgYjIyImNzc2NjMzMhYXFxYUIyM2FhcXFgYHBwYmJycmNjc3AqhIh15fh0dIiF5ehkj91jpyU1NvOYB8fYEBTggDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpHQUBCgIDBuMFBQEFAQIC5feqV1apenysWVise3CWS0qWcaqrq6oB5wMDMAYGMAMDBgRPBAUFA1AFBfYCAiAFBQJKAgMEDwQFAVv//wBO/1sCqAO6ACIAaAAAACMCPwGhAAAABwI1AgkA1AAEAE7/9gKoBEoADwAdADkASQAAJAYGIyImJjU0NjYzMhYWFQQWFjMyNjY1NCYjIgYVACYnJyYiBwcGBiMjIiY3NzY2MzMyFhcXFhQjIyc2NhcXFhYHBwYGJycmJjcCqEiHXl+HR0iIXl6GSP3WOnJTU285gHx9gQFOCAM7BwQIPgQHBCUFAQRjBAoGEgUIA2IFBinAAQUC5QICAQUBBQXjBgMC96pXVql6fKxZWKx7cJZLSpZxqqurqgHnAwMwBgYwAwMGBE8EBQUDUAUF7gICAVsBBQQPBAMCSgIFBQAEAE7/9gKoBEoADwAdADkAVgAAJAYGIyImJjU0NjYzMhYWFQQWFjMyNjY1NCYjIgYVACYnJyYiBwcGBiMjIiY3NzY2MzMyFhcXFhQjIzYGBwYGFxcWNzYzMhYVFAYHBhQXFxY3NjY1NCYjAqhIh15fh0dIiF5ehkj91jpyU1NvOYB8fYEBTggDOwcECD4EBwQlBQEEYwQKBhIFCANiBQYpFCUPAgEBCgIDHxMUFhcUAgEOBAIbHScg96pXVql6fKxZWKx7cJZLSpZxqqurqgHnAwMwBgYwAwMGBE8EBQUDUAUF8g0JAQQCFwQCFA8SDCAUAgQBEQMCGysVHiMAAAQATv/2AqgENQAPAB0AOQBcAAAkBgYjIiYmNTQ2NjMyFhYVBBYWMzI2NjU0JiMiBhUAJicnJiIHBwYGIyMiJjc3NjYzMzIWFxcWFCMjNgYHBgYjIiYmIyIGBgcGJicnJjY3NjYzMhYWMzI2NzYWFxcCqEiHXl+HR0iIXl6GSP3WOnJTU285gHx9gQFOCAM7BwQIPgQHBCUFAQRjBAoGEgUIA2IFBikvAgQOHRgWJyQRDhMQBwUEAgcBAQITHRQSKikSDxQPBQYCCPeqV1apenysWVise3CWS0qWcaqrq6oB5wMDMAYGMAMDBgRPBAUFA1AFBbwEAgkKDAwGCgQCAQUQAwQBDQ0NDAcJAwEFEgD//wBO//YCqAPEACIAaAAAAAcCPAH9ANT//wBO//YCqAO6ACIAaAAAAAcCLwHXANT//wBO/1sCqALwACIAaAAAAAMCPwGhAAD//wBO//YCqAPEACIAaAAAAAcCMQIMANT//wBO//YCqAPHACIAaAAAAAcCOwKcANT//wBO//YCqAMjACIAaAAAAAcCPgIRANQABABO//YCqAPIAA8AHQArADsAACQGBiMiJiY1NDY2MzIWFhUEFhYzMjY2NTQmIyIGFQEyFgcGBiM3MjY3NjYzJhYXFxYGBwcGJicnJjY3NwKoSIdeX4dHSIheXoZI/dY6clNTbzmAfH2BAb4GAwILPTUFHyAIAQYDLgUBCgIDBuMFBQEFAQIC5feqV1apenysWVise3CWS0qWcaqrq6oBsgUGIzcpIBgDAaUCAiAFBQJKAgMEDwQFAVsABABO/1sCqAMjAA8AHQArADsAACQGBiMiJiY1NDY2MzIWFhUEFhYzMjY2NTQmIyIGFQEyFgcGBiM3MjY3NjYzAhYVFRQGIyMiJjU1NDYzMwKoSIdeX4dHSIheXoZI/dY6clNTbzmAfH2BAb4GAwILPTUFHyAIAQYDiQQECB0HBAQFIveqV1apenysWVise3CWS0qWcaqrq6oBsgUGIzcpIBgDAfyfBAZRCAQEBlMGBAAEAE7/9gKoA8QADwAdACsAOwAAJAYGIyImJjU0NjYzMhYWFQQWFjMyNjY1NCYjIgYVATIWBwYGIzcyNjc2NjMlNjYXFxYWBwcGBicnJiY3AqhIh15fh0dIiF5ehkj91jpyU1NvOYB8fYEBvgYDAgs9NQUfIAgBBgP+9QEFAuUCAgEFAQUF4wYDAveqV1apenysWVise3CWS0qWcaqrq6oBsgUGIzcpIBgDAZ0CAgFbAQUEDwQDAkoCBQUAAAQATv/2AqgDxwAPAB0AKwBIAAAkBgYjIiYmNTQ2NjMyFhYVBBYWMzI2NjU0JiMiBhUBMhYHBgYjNzI2NzY2MyYGBwYGFxcWNzYzMhYVFAYHBhQXFxY3NjY1NCYjAqhIh15fh0dIiF5ehkj91jpyU1NvOYB8fYEBvgYDAgs9NQUfIAgBBgO6JQ8CAQEKAgMfExQWFxQCAQ4EAhsdJyD3qldWqXp8rFlYrHtwlktKlnGqq6uqAbIFBiM3KSAYAwGkDQkBBAIXBAIUDxIMIBQCBAERAwIbKxUeIwAABABO//YCqAOvAA8AHQArAE4AACQGBiMiJiY1NDY2MzIWFhUEFhYzMjY2NTQmIyIGFQEyFgcGBiM3MjY3NjYzJgYHBgYjIiYmIyIGBgcGJicnJjY3NjYzMhYWMzI2NzYWFxcCqEiHXl+HR0iIXl6GSP3WOnJTU285gHx9gQG+BgMCCz01BR8gCAEGAxwCBA4dGBYnJBEOExAHBQQCBwEBAhMdFBIqKRIPFA8FBgII96pXVql6fKxZWKx7cJZLSpZxqqurqgGyBQYjNykgGAMBawQCCQoMDAYKBAIBBRADBAENDQ0MBwkDAQUSAP//AE7/9gKoA8QAIgBoAAAABwIzAl4A1P//AE7/9gKoA8IAIgBoAAAABwI9AggA1P//AE7/9gKoA6YAIgBoAAAABwI6AgUA1P//AE7/VQKoAvAAIgBoAAAABwJCAfD//QADABX/9gKvAvAAIQApADMAAAEWFgcHFhUUBgYjIicHBgYnJyYmNzcmNTQ2NjMyFzc2NhcEBhUUFwEmIxI2NjU0JwEWFjMCrAIBAlU3SIdejE1JBAkGEQUCA1Q3SIheiU9LAwYE/lR/JQGQP3tUbjcl/nAgXz4C1wIGA2NfmXqqV1tUBgEFEAUGBGFdl3ysWV1XBAIDKKqqgk4B0FT9XEqVcYNQ/jEqKv//ABX/9gKvA8gAIgCAAAAABwIyAesA1P//AE7/9gKoA68AIgBoAAAABwI5AgoA1AACAE7/9gQuAvAAPABKAAAlMhYVFRQGIyEiJjU1BgYjIiYmNTQ2NjMyFhc1NDYzITIVFRQGIyEiBhURFBYzITIVFRQGIyEiBhURFBYzJBYWMzI2NTU0JiMiBhUEJgUDAwT+WwUEJX9aX4dHSIheWIElBAUBlQcDBf6aBQQEBQEWBwMF/usFBAQF/c06clN7gIV3fYEoAwQaBAMEBnlJRFapenysWUxLgwYEBxoEAwMG/t0EAwcaBAMDBv7XBgPZlkuak2R/lquqAAIAcgAAAmMC5gAXACYAAAAWFhUUBgYjIyIVERQGIyMiJjURNDYzMxI2NTQmIyMiBhURFBYzMwGpd0NCdE63CAQEHgQEBAXea29wa68FBAMEuALmK1xISF0rCf7LBAUEBALUBgT+iVJZWUsEBP7DBQUAAAIAcgAAAmMC5gAeAC0AAAAWFhUUBgYjIyIVFRQGIyMiJjURNDYzMzIWFRUUMzMSNjU0JiMjIgYVERQWMzMBqXdDQnROtwgEBB4EBAQFHAUEBrNrb3BrrwUEAwS4AkMoVUBJXSoJpAQFBAQC1AYEBAaUBf6bUFhQRQQE/tUFBQAAAgBO/4YCqALwACEALwAABCYmNTQ2NjMyFhYVFAYHBhUUFhYXFhUVFAYjIiYnJgcGIwIGFRQWFjMyNjY1NCYjAR6HSUiHXl6HSE5LBilCIwcJCydbLQUKJSx9gDpxUlNwOoB9ClWqfHysV1esfIOoLQQFDiojBQEGFggFNj8HAwkC0K6pcJVKSpZxqK0AAAIAcgAAAmEC5gAkADMAACUWBiMjIiYnAyYmIyMiBhURFAYjIyImNRE0NjMhMhYVFAYHBhcnMjY1NCYjIyIGFREUFjMCVwIFBRwFBAOOAgkK2AYGBAYaBgQEBQECbXdZPwYCa21qWlvRBgUDBA4FCQQFAVAFBQUG/rMGBQQFAtMGBF5aVFwQAQYkSlJMSwQE/t4FBP//AHIAAAJhA8gAIgCHAAAABwIyAdYA1P//AHIAAAJhA7oAIgCHAAAABwI2AdoA1P//AHL/FAJhAuYAIgCHAAAAAwJAAX8AAP//AHIAAAJhA8QAIgCHAAAABwI8Ac4A1P//AHL/WwJhAuYAIgCHAAAAAwI/AXkAAP//AHIAAAJhA8IAIgCHAAAABwI9AdkA1AABADn/9gIpAvAANAAAACYnJiYjIgYVFBcXFhYVFAYGIyImJyY2Nzc2MhcWFjMyNjU0JicnJiY1NDY2MzIWFxYWBwcB/QQFIF08U2R6ilZLO25LU3ksBAMHCwcJBC1aTF5oN02XSz86aUVDaS0DAQQWAmsCBiYtUEleLDIfW0E8Vy08PwUKBQgFBTozR00wQB46HF03PVkuMS8DCAMU//8AOf/2AikDyAAiAI4AAAAHAjIBxwDU//8AOf/2AikDugAiAI4AAAAHAjYBywDU//8AOf80AikC8AAiAI4AAAADAkEBlgAA//8AOf/2AikDugAiAI4AAAAHAjUBywDU//8AOf8UAikC8AAiAI4AAAADAkABbQAA//8AOf9bAikC8AAiAI4AAAADAj8BZwAAAAIATv/2AqcC8AAjAC0AAAA2NTQmIyIGBjcGIicnJjY3PgIzMhYWFRQGBiMiJjU0NjMhAjY3ISIGFRQWMwJxAmiQXmVeMAEDAyYDAQEdInpmdIYyOoZso4oDBQIXbG8D/hcEBHKHAXwCBH7FUulqAQENAQUDS0JZdaxhZalq1KcGBf6lroMDA32uAAEALwAAAkAC5gAfAAASJjU1NDYzITIWFRUUBiMjIgYVERQGIyMiJjURNCYjIzIDAwQCAwQDAwXVBgQEBRsGBAQG6gK+AwQaBAMDBBoEAwMG/VUGBAQGAqsGAwAAAgAvAAACQALmAA4ALgAAABYVFRQGIyEiJjU1NDMhACY1NTQ2MyEyFhUVFAYjIyIGFREUBiMjIiY1ETQmIyMB9QMGB/6pBgQHAWD+QQMDBAIDBAMDBdUGBAQFGwYEBAbqAZIEBBcGBQUFFwkBLAMEGgQDAwQaBAMDBv1VBgQEBgKrBgP//wAvAAACQAO6ACIAlgAAAAcCNgHPANT//wAv/zQCQALmACIAlgAAAAMCQQGWAAD//wAv/xQCQALmACIAlgAAAAMCQAFtAAD//wAv/1sCQALmACIAlgAAAAMCPwFnAAAAAQBo//YCVgLmABsAACUUBiMiJjURNDYzMzIWFREUFjMyNjURNDMzMhUCVnt8fnkEBBwGBGVmZ2YIGAjmeXd9ggHmBgUEBv4GYWFiYwH4CQkA//8AaP/2AlYDyAAiAJwAAAAHAjIB6wDU//8AaP/2AlYDugAiAJwAAAAHAjcB7wDU//8AaP/2AlYDugAiAJwAAAAHAjUB7wDU//8AaP/2AlYDxAAiAJwAAAAHAjwB4wDU//8AaP/2AlYDugAiAJwAAAAHAi8BvQDU//8AaP9bAlYC5gAiAJwAAAADAj8BgwAA//8AaP/2AlYDxAAiAJwAAAAHAjEB8gDU//8AaP/2AlYDxwAiAJwAAAAHAjsCggDU//8AaP/2ArgDIwAiAJwAAAAHAj4ChADUAAMAaP/2ArgDyAAbACkAOQAAJRQGIyImNRE0NjMzMhYVERQWMzI2NRE0MzMyFTcyFgcGBiM3MjY3NjYzJhYXFxYGBwcGJicnJjY3NwJWe3x+eQQEHAYEZWZnZggYCFkGAwILPTUFHyAIAQYDuwUBCgIDBuMFBQEFAQIC5eZ5d32CAeYGBQQG/gZhYWJjAfgJCUYFBiM3KSAYAwGlAgIgBQUCSgIDBA8EBQFbAAADAGj/WwK4AyMAGwApADkAACUUBiMiJjURNDYzMzIWFREUFjMyNjURNDMzMhU3MhYHBgYjNzI2NzY2MwAWFRUUBiMjIiY1NTQ2MzMCVnt8fnkEBBwGBGVmZ2YIGAhZBgMCCz01BR8gCAEGA/7mBAQIHQcEBAUi5nl3fYIB5gYFBAb+BmFhYmMB+AkJRgUGIzcpIBgDAfyfBAZRCAQEBlMGBAADAGj/9gK4A8QAGwApADkAACUUBiMiJjURNDYzMzIWFREUFjMyNjURNDMzMhU3MhYHBgYjNzI2NzY2MyU2NhcXFhYHBwYGJycmJjcCVnt8fnkEBBwGBGVmZ2YIGAhZBgMCCz01BR8gCAEGA/5oAQUC5QICAQUBBQXjBgMC5nl3fYIB5gYFBAb+BmFhYmMB+AkJRgUGIzcpIBgDAZ0CAgFbAQUEDwQDAkoCBQUAAwBo//YCuAPHABsAKQBGAAAlFAYjIiY1ETQ2MzMyFhURFBYzMjY1ETQzMzIVNzIWBwYGIzcyNjc2NjMkBgcGBhcXFjc2MzIWFRQGBwYUFxcWNzY2NTQmIwJWe3x+eQQEHAYEZWZnZggYCFkGAwILPTUFHyAIAQYD/rklDwIBAQoCAx8TFBYXFAIBDgQCGx0nIOZ5d32CAeYGBQQG/gZhYWJjAfgJCUYFBiM3KSAYAwGkDQkBBAIXBAIUDxIMIBQCBAERAwIbKxUeIwAAAwBo//YCuAOvABsAKQBMAAAlFAYjIiY1ETQ2MzMyFhURFBYzMjY1ETQzMzIVNzIWBwYGIzcyNjc2NjMmBgcGBiMiJiYjIgYGBwYmJycmNjc2NjMyFhYzMjY3NhYXFwJWe3x+eQQEHAYEZWZnZggYCFkGAwILPTUFHyAIAQYDqQIEDh0YFickEQ4TEAcFBAIHAQECEx0UEiopEg8UDwUGAgjmeXd9ggHmBgUEBv4GYWFiYwH4CQlGBQYjNykgGAMBawQCCQoMDAYKBAIBBRADBAENDQ0MBwkDAQUS//8AaP/2AlYDxAAiAJwAAAAHAjMCRADU//8AaP/2AlYDwgAiAJwAAAAHAj0B7gDU//8AaP/2AlYDpgAiAJwAAAAHAjoB6wDU//8AaP9VAlYC5gAiAJwAAAAHAkIB1P/9//8AaP/2AlYDxAAiAJwAAAAHAjgB1QCl//8AaP/2AlYDrwAiAJwAAAAHAjkB8ADUAAEAMwAAAn8C5gAaAAABMhYHAQYGIyMiJwMmNjMzMhYXExY2NxM2NjMCdgQFAv76AgcHIBAD/wIFBRcGBQLtBAgE9AIFBALmBQT9LgYFCgLSBQUDBf1iCgEKAp4EAwABADYAAAPIAuYAMAAAATIWBwMGBiMjIicDJiIHAwYGIyMiJicDJjYzMzIWFxMWNjcTNjMzMhYXExY2NxM2MwPABAQBzwIHByAPA7UBAgG5AgcHIAgJAcoCBQUZBgQCuQMGA70CCxkGBAK5AwYDvQELAuYFBP0uBgUKAogDA/15BgUFBQLSBQUDBf1pCgEKApcHAwX9aQoBCgKXB///ADYAAAPIA8gAIgCyAAAABwIyAokA1P//ADYAAAPIA7oAIgCyAAAABwI1Ao0A1P//ADYAAAPIA7oAIgCyAAAABwIvAlsA1P//ADYAAAPIA8QAIgCyAAAABwIxApAA1AABADMAAAJjAuYALQAAJRYGIyMiJwMmBgcDBiMjIiY3EzYmJwMmNjMzMhYXExYyNxM2NjMzMhQHAwYGFwJfBAMFJAQD3AUJB9kDBSQFAgTyBAEE6AICAycEBAHPBQcFzwEEAykDAeoDAQMKBgQEAUMJAQv+wAQEBgFoBQoGAVcCBgIC/swGBwEzAgIEAv6mBQcEAAEALwAAAk8C5gAjAAABMhYHAwYGFREUBiMjIiY1ETQmJwMmNjMzMhYXExYyNxM2NjMCRgUEA+8DAQcIEwYGAgP0AwQHFwcGA9IHCwbRBAUHAuYHBP5rBAkH/tsHBgYFARwGCQUBnwUHBAX+mwwKAWcGAwD//wAvAAACTwPIACIAuAAAAAcCMgHCANT//wAvAAACTwO6ACIAuAAAAAcCNQHGANT//wAvAAACTwO6ACIAuAAAAAcCLwGUANT//wAv/1sCTwLmACIAuAAAAAMCPwFjAAD//wAvAAACTwPEACIAuAAAAAcCMQHJANT//wAvAAACTwPHACIAuAAAAAcCOwJZANT//wAvAAACTwOvACIAuAAAAAcCOQHHANQAAQA3AAACIALmACYAADMiJjU1NDY3ATYmIyEiJjU1NDYzITIWFRUUBgcBBhYzITIVFRQGIz8EBAQDAaQCAgT+agQDAwMB0wMDAwL+WAIBAwGlBgMEAwQPBAgFApAEAwMEGwMDAwMRAwYD/W0EBAUcAwT//wA3AAACIAPIACIAwAAAAAcCMgG5ANT//wA3AAACIAO6ACIAwAAAAAcCNgG9ANT//wA3AAACIAO6ACIAwAAAAAcCMAFUANT//wA3/1sCIALmACIAwAAAAAMCPwFcAAAAAgAw//YByQIcACsAPQAAJCYHBiMiJjU0NjY3NzY1NTQmIyIGBwYGJycmJjc2NjMyFhURFAYjIyImNScnNCYHBw4CFRQWMzI2NzY2NQGhBQRSbE9bN25SaRFRRDNTIgIFAxgCAQMiZkRVZgUFEgUEAgEHB1lOYy9JPSRHHRwdSwEEUk5DMkkrBwkCDR9ASy4uAwMCDwIEBDI7XVD+mwYEBAU9xAgHAQcGIjgnMzsaFxYxFwD//wAw//YByQL0ACIAxQAAAAMCMgGSAAD//wAw//YByQLmACIAxQAAAAMCNwGWAAD//wAw//YByQOmACIAxQAAACMCNwGWAAAABwIyAZAAsv//ADD/WwHJAuYAIgDFAAAAIwI/ATEAAAADAjcBlgAA//8AMP/2AckDogAiAMUAAAAjAjcBlgAAAAcCMQGXALL//wAw//YByQOlACIAxQAAACMCNwGWAAAABwI7AicAsv//ADD/9gHJA40AIgDFAAAAIwI3AZYAAAAHAjkBlQCy//8AMP/2AckC5gAiAMUAAAADAjUBlgAA//8AMP/2AckDegAiAMUAAAAjAjUBlgAAAAcCMgGRAIb//wAw/1sByQLmACIAxQAAACMCPwExAAAAAwI1AZYAAP//ADD/9gHJA3YAIgDFAAAAIwI1AZYAAAAHAjEBmACG//8AMP/2AckDdgAiAMUAAAAjAjUBlgAAAAcCOwKrAIP//wAw//YByQNhACIAxQAAACMCNQGWAAAABwI5AZYAhv//ADD/9gHJAvAAIgDFAAAAAwI8AYoAAP//ADD/9gHJAuYAIgDFAAAAAwIvAWQAAP//ADD/WwHJAhwAIgDFAAAAAwI/ATEAAP//ADD/9gHJAvAAIgDFAAAAAwIxAZkAAP//ADD/9gHJAvMAIgDFAAAAAwI7AikAAP//ADD/9gHJAu4AIgDFAAAAAwI9AZUAAP//ADD/9gHJAtIAIgDFAAAAAwI6AZIAAP//ADD/WgHmAhwAIgDFAAAABwJCAfAAAv//ADD/9gHJAx8AIgDFAAAAAwI4AXgAAP//ADD/9gHJA8IAIgDFAAAAJwIyAZIAzgAHAjgBj//3//8AMP/2AckC2wAiAMUAAAADAjkBlwAAAAMAMP/2A3ECHABFAFEAYAAAJSIGFRQWFjMyNjc2NhcXFhYHBgYjIiYnJgcOAiMiJjU0Njc3NjU1NCYjIgYHBgYnJyY0Nz4CMzIWFzY2MzIWFhUUBiMuAiMiBgYVBTI2NQU0JgcHBgYVFBY3PgI1AdcNBzZcNkVNHgQDBBYCAQIac0dQWyICAxo0VzxOWHZ5cBJQSi9SIwQEAxYCAhVATipKVg8eaUdFZzkIChkxVjY3WTQBcAsG/lsGCV9vaUc+NVg0/QkQL108MyoFAQIPAgQEMT1EMgQEITAlTkRLVwoJAg0fQUouLgUBAg8CBQMiMRo7PDs8P3dUCgpqWTU1YD4BBgopCAcBBwlCOzM9AwIqQiYA//8AMP/2A3EC9AAiAN4AAAADAjICTQAAAAIAW//2AhUC5gAhAC8AADYGIyMiJjURNDYzMzIVFRQWNzY2MzIWFRQGIyImJyYGFQcSBgcVFBYWMzI2NTQmI4AGCQsGBQQFFwgGBxtOQ21seWtCURIEBgKAXSAxTypTal5XBQUGBwLPBQUL/QkCBhgrmXiBlDMaBQEHMwHpMCfuJ0UpenVtfgABADj/9gH2AhwAKwAAEjY2MzIWFhcWBwcGJicmJiMiBgYVFBYWMzI2Njc2NhcXFhYHDgIjIiYmNTg9bUYrSDkYBQUYAgYDKUgrPVwyMlw8IjszFgMEAxcCAQEYP0srR208AVd+RxkwIgcDEAICBDEqO2xGRmw7FiwiBQECDwIFAyc0GkR8UQD//wA4//YB9gL0ACIA4QAAAAMCMgGvAAD//wA4//YB9gLmACIA4QAAAAMCNgGzAAD//wA4/zQB9gIcACIA4QAAAAMCQQF8AAD//wA4//YB9gLmACIA4QAAAAMCNQGzAAD//wA4//YB9gLmACIA4QAAAAMCMAFKAAAAAgA5//YB9QLmACMAMwAAISImNTU0JgcGBiMiJiY1NDY2MzIWFxY2NTU0MzMyFhURFAYjAyYmIyIGBhUUFhYzMjY2NQHZBQUGBhxKPUFpPTxuSTFCIAkFCRcEBAUHHCZOLDtbNDVYNS5NLQYFMwgCBh0vPHdWVIJHHhgHBArvCgUE/S8HBQGqJiY7bkpMaDMtQh8AAAIAOP/2Ah4C8AAvAD8AAAAWFRQGBiMiJiY1NDY2MzIWFyYnBwYmJycmNjc3JicmJjc3NjYXFhc3NjIXFxYHBwIWFjMyNjY1NCYmIyIGBhUBu2M7b0tHbjw8a0M0WCEyeKgEBgMJAwEDmzY7BAEEEgUHBkIyaAIGAQwDBFrpNV05OFs0NFw4OF01AlHOhE14REN3TE16RSQhdFFfAgIFDgQFAlceGAIGAwwEAQMbHjoBAhYFAzL+I2g8PGc/P2g9PWk+AP//ADn/9gJ4AuYAIgDnAAAAAwI0AoIAAAADADn/9gJBAuYADgAyAEIAAAAWFRUUBiMhIiY1NTQzIQMiJjU1NCYHBgYjIiYmNTQ2NjMyFhcWNjU1NDMzMhYVERQGIwMmJiMiBgYVFBYWMzI2NjUCPgMFBf7wBAMFARdjBQUGBhxKPUFpPTxuSTFCIAkFCRcEBAUHHCZOLDtbNDVYNS5NLQKWAwMVBAQDBRQH/WoGBTMIAgYdLzx3VlSCRx4YBwQK7woFBP0vBwUBqiYmO25KTGgzLUIf//8AOf9bAfUC5gAiAOcAAAADAj8BUgAAAAIAOP/2AhACHAAiAC4AADYVFBYWMzI2NzY2FxcWFgcGBiMiJiY1NDY2MzIWFhUUBiMhNgYGFyEyNjU0JiYjYjZcNztZGwMFAxcCAQIjaUZHbz0+bkdGZzgDCv5lg1ovAQFyCgcuVTn+DjhiOjAtBAICDwIFAzc3RHtQVn5DP3hTCgr4PmI0Bgg3WzT//wA4//YCEAL0ACIA7AAAAAMCMgGxAAD//wA4//YCEALmACIA7AAAAAMCNwG1AAD//wA4//YCEALmACIA7AAAAAMCNgG1AAD//wA4//YCEALmACIA7AAAAAMCNQG1AAD//wA4//YCEAN6ACIA7AAAACMCNQG1AAAABwIyAbAAhv//ADj/WwIQAuYAIgDsAAAAIwI/AVAAAAADAjUBtQAA//8AOP/2AhADdgAiAOwAAAAjAjUBtQAAAAcCMQG3AIb//wA4//YCEAN2ACIA7AAAACMCNQG1AAAABwI7AsoAg///ADj/9gIQA2EAIgDsAAAAIwI1AbUAAAAHAjkBtQCG//8AOP/2AhAC8AAiAOwAAAADAjwBqQAA//8AOP/2AhAC5gAiAOwAAAADAi8BgwAA//8AOP/2AhAC5gAiAOwAAAADAjABEwAA//8AOP9bAhACHAAiAOwAAAADAj8BUAAA//8AOP/2AhAC8AAiAOwAAAADAjEBuAAA//8AOP/2AhAC8wAiAOwAAAADAjsCSAAA//8AOP/2AhAC7gAiAOwAAAADAj0BtAAA//8AOP/2AhAC0gAiAOwAAAADAjoBsQAA//8AOP9eAhACHAAiAOwAAAAHAkIBtgAG//8AOP/2AhAC2wAiAOwAAAADAjkBtgAAAAIAMf/2AgkCHAAiAC4AAAA1NCYmIyIGBwYGJycmJjc2NjMyFhYVFAYGIyImJjU0NjMhBjY2JyEiBhUUFhYzAd82XDc7WRsDBQMXAgECI2lGR289Pm5HRmc4AwoBm4NaLwH+jgoHLlU5ARQOOGI6MC0EAgIPAgUDNzdEe1BWfkM/eFMKCvg+YjQGCDdbNAABABoAAAFXAuYAMAAAATIWFRUUIyMiFREUIyMiNRE0IyMiNTU0NjMzMjY1NTQ2MzMyFhUVFAYjIyIGFRUUMwFNAgMGhQcJFwgGcgYDA3EEAzY7SAMDBARQJBsHAhIDAxcGB/4jCwoB3gcGFwMDAwRRPz0DBBUEAycpWgcAAwAj/1ECKAIaAEEATQBbAAAANjMyFhUVFAYHBgYHBgYXHgIVFAYGIyIGFRQWFxcWFhUUBgYjIiY1NDY3NicmNTQ2NzYmJyYmNTQ2MzIWFxYWNwQWMzI2NTQmIyIGFQAmJycmBgYVFBYzMjY1AdYnGAsIBwoTGAwNCQIECgcrW0RPRTI7eE5MOGxLc30tLwsLJiAjBgEGKSZvYzBFGgUIBf61VU9RUFJPTlYBZDQ9sBEzJmxjXV8B/RUGBw0HCAEBBgcHCwUJGB4TLkgqHBUSEAMHBEY9MkYkTkYkPxoGBREhEiMLAQgDF0AvUFkWFwQBBLVAPj4/QkQ9/qUvBAsBHTIfNzs6PAD//wAj/1ECKALmACIBAgAAAAMCNwGnAAD//wAj/1ECKALmACIBAgAAAAMCNQGnAAAABAAj/1ECKAMSABkAWwBnAHUAAAEiNTU0NjU3NjYzMzIWBwcGFjMzMhYVFRQjFjYzMhYVFRQGBwYGBwYGFx4CFRQGBiMiBhUUFhcXFhYVFAYGIyImNTQ2NzYnJjU0Njc2JicmJjU0NjMyFhcWFjcEFjMyNjU0JiMiBhUAJicnJgYGFRQWMzI2NQEBBgEfAgYGDQMDAhkCAgQIBQMHqCcYCwgHChMYDA0JAgQKBytbRE9FMjt4Tkw4bEtzfS0vCwsmICMGAQYpJm9jMEUaBQgF/rVVT1FQUk9OVgFkND2wETMmbGNdXwJ4B0wCAwE7BAIFAzYEBQQFQgh7FQYHDQcIAQEGBwcLBQkYHhMuSCocFRIQAwcERj0yRiRORiQ/GgYFESESIwsBCAMXQC9QWRYXBAEEtUA+Pj9CRD3+pS8ECwEdMh83Ozo8//8AI/9RAigC5gAiAQIAAAADAjABPgAAAAEAWwAAAewC5gAmAAAzIiY1ETQ2MzMyFREUFjc2NjMyFhURFCMjIiY1ETQmIyIGBxEUBiNkBAUEBBUIBARBTjNOVAkWBAU8PzJRQwQFBQQC1AUECP7uBQIELiVQQ/6BCgUEAXA9QCkz/m8EBQAAAgAUAAAB7ALmAA4ANQAAABYVFRQGIyEiJjU1NDMhAyImNRE0NjMzMhURFBY3NjYzMhYVERQjIyImNRE0JiMiBgcRFAYjATIDBQX+8AQDBQEXzAQFBAQVCAQEQU4zTlQJFgQFPD8yUUMEBQKWAwMVBAQDBRQH/WoFBALUBQQI/u4FAgQuJVBD/oEKBQQBcD1AKTP+bwQFAP//AFsAAAHsA8QAIgEHAAAABwI1Aa0A3v//AFv/WwHsAuYAIgEHAAAAAwI/AVMAAAACAFYAAACIAuYADwAfAAASFhURFAYjIyImNRE0NjMzNhYVFRQGIyMiJjU1NDYzM38EBAgRBwQEBRYHBwUHGwYFBwUaAhIEBv4EBwUEBgH+BgTUBwZFCAgHB0cGBwAAAQBbAAAAgwISAA8AABIWFREUBiMjIiY1ETQ2MzN/BAQIEQcEBAUWAhIEBv4EBwUEBgH+BgT////1AAAA8gL0ACIBDAAAAAMCMgD3AAD////uAAAA8gLmACIBDAAAAAMCNwD7AAD////sAAAA8QLmACIBDAAAAAMCNQD7AAD///+sAAAA0gLwACIBDAAAAAMCPADvAAD//wAeAAAAvwLmACIBDAAAAAMCLwDJAAD//wBWAAAAiALmACIBDAAAAAMCMACSAAD//wBW/1sAigLmACIBCwAAAAMCPwCUAAD////3AAAA9ALwACIBDAAAAAMCMQD+AAD//wAdAAAAqQLzACIBDAAAAAMCOwGOAAD////tAAAA8QLuACIBDAAAAAMCPQD6AAD////xAAAA7QLSACIBDAAAAAMCOgD3AAD////v/1kAngLmACIBDAAAACMCMACSAAAABwJCAKgAAf///+wAAADyAtsAIgEMAAAAAwI5APwAAAAC/9H/VgCIAuYAFQAlAAAXFAYjIiY1NTQ2Mzc2NjURNDYzMzIVJhYVFRQGIyMiJjU1NDYzM4MvQiwVAwM6KCIFBRYIAgcFBxsGBQcFGjNANwMGFQMDAQEnLwI2BQUK3gcGRQgIBwdHBgcAAf/R/1YAgwISABUAABcUBiMiJjU1NDYzNzY2NRE0NjMzMhWDL0IsFQMDOigiBQUWCDNANwMGFQMDAQEnLwI2BQUKAP///9H/VgDxAuYAIgEbAAAAAwI1APsAAAABAFsAAAH8AuYALQAAJRYGIyMiJwMmIgcHBhUVFAYjIyImNRE0NjMzMhYVERQWNwE2NjMzMhYHBwYGFwH4BAIHHwgEtgIDA4AHBAgRBwQEBRYFBAQGARwFBwUgCAEHqQIBAgwGBgcBJAMDggcKjAcFBAYC0gYEBAb+DgkCBgEjBgQIB60CBQT//wBb/xQB/ALmACIBHQAAAAMCQAE7AAAAAQBbAAAB/AISAC0AACUWBiMjIicDJiIHBwYVFRQGIyMiJjURNDYzMzIWFREUFjcBNjYzMzIWBwcGBhcB+AQCBx8IBLYCAwOABwQIEQcEBAUWBQQEBgEcBQcFIAgBB6kCAQIMBgYHASQDA4IHCowHBQQGAf4GBAQG/uIJAgYBIwYECAetAgUEAAEAWwAAAIMC5gAPAAASFhURFAYjIyImNRE0NjMzfwQECBEHBAQFFgLmBAb9MAcFBAYC0gYE////9QAAAPIDyAAiASAAAAAHAjIA9wDU//8AWwAAAQUC5gAiASAAAAADAjQBDwAA//8AT/8UAI8C5gAiASAAAAADAkAAmQAA//8AWwAAAS0C5gAiASAAAAAHAc4Arv+1AAIAAwAAAPAC5gAMABwAABIVFRQHBwYmNTU0NzcCFhURFAYjIyImNRE0NjMz8AbgAwQF4WAEBAgRBwQEBRYBuQgeBQbTAwQEHQUF0gEzBAb9MAcFBAYC0gYEAAEAWwAAAy0CHABAAAAzIiY1ETQ2MzMyFRUUFjc2NjMyFhcWNjc2NjMyFhURFCMjIiY1ETQmIyIGBwYGFREUIyMiJjURNCYjIgYHERQGI2QEBQQEFQgEBD5KMDpJCwEJBT5KMElPCRYEBTc6MFArCgcJFgQFNzovTUAEBQUEAgAFBAg+BQIELiUzIwQDBC4lUEP+gQoFBAFwPUAsIwgMCv6BCgUEAXA9QCkz/m8EBQAAAQBbAAAB7AIcACYAADMiJjURNDYzMzIVFRQWNzY2MzIWFREUIyMiJjURNCYjIgYHERQGI2QEBQQEFQgEBEFOM05UCRYEBTw/MlFDBAUFBAIABQQIPgUCBC4lUEP+gQoFBAFwPUApM/5vBAX//wBbAAAB7AL0ACIBJwAAAAMCMgG3AAD//wBbAAAB7ALmACIBJwAAAAMCNgG7AAD//wBb/xQB7AIcACIBJwAAAAMCQAFPAAD//wBbAAAB7ALmACIBJwAAAAMCMAFSAAAAAQBb/1YB7AIcAC0AABI2MzIWFREUBiMiJjU1NDYzNzY2NRE0JiMiBxEUBiMjIiY1ETQ2MzMyFRUUFjfIUjBMVjY7JxMDAzMoIjtEWGoEBRYEBQQEFQgEBAH5I1FC/k4/QgMGEwMDAQEoMAGhP0Vf/nEEBQUEAgAFBAg/BQIEAP//AFsAAAHsAtsAIgEnAAAAAwI5AbwAAAACADj/9gIeAhwADwAeAAAkBgYjIiYmNTQ2NjMyFhYVBBYWMzI2NTQmJiMiBgYVAh49bkhJbjw9b0hHbj3+RDJcPVxrMVs8PVsyuH1FRXtQUX5HRn5QRms7gWtHazw8bEYA//8AOP/2Ah4C9AAiAS4AAAADAjIBtQAA//8AOP/2Ah4C5gAiAS4AAAADAjcBuQAA//8AOP/2Ah4C5gAiAS4AAAADAjUBuQAA//8AOP/2Ah4DegAiAS4AAAAjAjUBuQAAAAcCMgG0AIb//wA4/1sCHgLmACIBLgAAACMCPwFOAAAAAwI1AbkAAP//ADj/9gIeA3YAIgEuAAAAIwI1AbkAAAAHAjEBuwCG//8AOP/2Ah4DdgAiAS4AAAAjAjUBuQAAAAcCOwLOAIP//wA4//YCHgNhACIBLgAAACMCNQG5AAAABwI5AbkAhv//ADj/9gIeAvAAIgEuAAAAAwI8Aa0AAP//ADj/9gIeAuYAIgEuAAAAAwIvAYcAAP//ADj/WwIeAhwAIgEuAAAAAwI/AU4AAP//ADj/9gIeAvAAIgEuAAAAAwIxAbwAAP//ADj/9gIeAvMAIgEuAAAAAwI7AkwAAP//ADj/9gIeAk8AIgEuAAAAAwI+AbkAAAAEADj/9gIeAvQADwAeACwAPAAAJAYGIyImJjU0NjYzMhYWFQQWFjMyNjU0JiYjIgYGFQEyFgcGBiM3MjY3NjYzJhYXFxYGBwcGJicnJjY3NwIePW5ISW48PW9IR249/kQyXD1cazFbPD1bMgGCBgMCCz01BR8gCAEGAyYFAQoCAwbjBQUBBQECAuW4fUVFe1BRfkdGflBGazuBa0drPDxsRgFHBQYjNykgGAMBpQICIAUFAkoCAwQPBAUBWwAABAA4/1sCHgJPAA8AHgAsADwAACQGBiMiJiY1NDY2MzIWFhUEFhYzMjY1NCYmIyIGBhUBMhYHBgYjNzI2NzY2MwIWFRUUBiMjIiY1NTQ2MzMCHj1uSEluPD1vSEduPf5EMlw9XGsxWzw9WzIBggYDAgs9NQUfIAgBBgOEBAQIHQcEBAUiuH1FRXtQUX5HRn5QRms7gWtHazw8bEYBRwUGIzcpIBgDAf1zBAZRCAQEBlMGBAAABAA4//YCHgLwAA8AHgAsADwAACQGBiMiJiY1NDY2MzIWFhUEFhYzMjY1NCYmIyIGBhUBMhYHBgYjNzI2NzY2MyU2NhcXFhYHBwYGJycmJjcCHj1uSEluPD1vSEduPf5EMlw9XGsxWzw9WzIBggYDAgs9NQUfIAgBBgP+/QEFAuUCAgEFAQUF4wYDArh9RUV7UFF+R0Z+UEZrO4FrR2s8PGxGAUcFBiM3KSAYAwGdAgIBWwEFBA8EAwJKAgUFAAQAOP/2Ah4C8wAPAB4ALABJAAAkBgYjIiYmNTQ2NjMyFhYVBBYWMzI2NTQmJiMiBgYVATIWBwYGIzcyNjc2NjMmBgcGBhcXFjc2MzIWFRQGBwYUFxcWNzY2NTQmIwIePW5ISW48PW9IR249/kQyXD1cazFbPD1bMgGCBgMCCz01BR8gCAEGA7IlDwIBAQoCAx8TFBYXFAIBDgQCGx0nILh9RUV7UFF+R0Z+UEZrO4FrR2s8PGxGAUcFBiM3KSAYAwGkDQkBBAIXBAIUDxIMIBQCBAERAwIbKxUeIwAEADj/9gIeAtsADwAeACwATwAAJAYGIyImJjU0NjYzMhYWFQQWFjMyNjU0JiYjIgYGFQEyFgcGBiM3MjY3NjYzJgYHBgYjIiYmIyIGBgcGJicnJjY3NjYzMhYWMzI2NzYWFxcCHj1uSEluPD1vSEduPf5EMlw9XGsxWzw9WzIBggYDAgs9NQUfIAgBBgMUAgQOHRgWJyQRDhMQBwUEAgcBAQITHRQSKikSDxQPBQYCCLh9RUV7UFF+R0Z+UEZrO4FrR2s8PGxGAUcFBiM3KSAYAwFrBAIJCgwMBgoEAgEFEAMEAQ0NDQwHCQMBBRL//wA4//YCHgLwACIBLgAAAAMCMwIOAAD//wA4//YCHgLuACIBLgAAAAMCPQG4AAD//wA4//YCHgLSACIBLgAAAAMCOgG1AAD//wA4/1YCHgIcACIBLgAAAAcCQgGY//4AAwAT//YCLAIcAA4AHgAtAAAAFgcBBiInJyY2NwE2FxcCBgYjIiYmNTQ2NjMyFhYVBBYWMzI2NTQmJiMiBgYVAisBBP4LBAkDCwUBBQHxCQgNDD1uSEluPD1vSEduPf5EMlw9XGsxWzw9WzIB+wYE/hMEBAwFCQUB6QkJDf65fUVFe1BRfkdGflBGazuBa0drPDxsRgD//wAT//YCLAL0ACIBRgAAAAMCMgGoAAD//wA4//YCHgLbACIBLgAAAAMCOQG6AAAAAwA4//YDygIcADQARABQAAAkBiMiJiY1NDY2MzIWFxYyNzY2MzIWFhUUBiMhIhUUFhYzMjY3NjYXFxYWBwYGIyImJyYiByQWFjMyNjY1NCYmIyIGBhUkJiYjIgYGFQUyNjUB4G5HSW48PW9ISm8dBAYDHHBHSWg3CAn+cw82XDY8WBsDBQMXAgECI2lGR3AeBAgG/mQzXDw8WjEyWzs8WzMDPzBWNzhaMgFwCgc5Q0R7UVJ+RklBBwdASkB5VAgJEjhfOC8tBAICDwIFAzc3RT0JDE1qOzprRkZrPDxrRl9aNDZgPAEGCAAAAgBb/1sCFAIcACQANAAAEzIWFRUUFjc+AjMyFhYVFAYGIyImJyYGFRUUIyMiJjURNDYzExYWMzI2NjU0JiYjIgYGFXcFBQYGFhxCL0FoOztsSS9KGggGCRcEBAUHHCdNLDpaMzRXNC9NLAISBgUzCAIGGBgcPHdWVYFHHRUHBAq8CgUEAqIHBf5WJSc7bkpMaDMrQB8AAgBb/1sCFALmACQANAAAEzIWFREUFjc+AjMyFhYVFAYGIyImJyYGFRUUIyMiJjURNDYzExYWMzI2NjU0JiYjIgYGFXcFBQYGFhxCL0FoOztsSS9KGggGCRcEBAUHHCdNLDpaMzRXNC9NLALmBgX++QgCBhgYHDx3VlWBRx0VBwQKvAoFBAN2BwX9giUnO25KTGgzK0AfAAACADr/WwH2AhwAIwAzAAAANjMzMhYVERQGIyMiNTU0JgcGBiMiJiY1NDY2MzIWJhcWNzcCNjc1NCYmIyIGBhUUFhYzAdUFCQgGBQQFFwgFCR1RL0dqODdoSDJDBTUJAQR9WB8sTi82VzQyWTkCDQUGB/1gBQULvAsDBxYdRX5TUnpEGQQuCQwp/hcpJPgmRSo1aUtKbToAAAEAWwAAAUoCHAAiAAAzIiY1ETQzMzIWFRUUFjc2NjMyFRUUBiMiJiMiBgYVERQGI2QEBQgVBQMEBC1JKSMDAgQPCyBONgQFBQQCAAkDBUQFAgQwKQoaAgMBKD8g/pwEBQD//wBRAAABTgL0ACIBTQAAAAMCMgFTAAD//wBIAAABTQLmACIBTQAAAAMCNgFXAAD//wBM/xQBSgIcACIBTQAAAAMCQACWAAD//wAIAAABSgLwACIBTQAAAAMCPAFLAAD//wBU/1sBSgIcACIBTQAAAAMCPwCSAAD//wBJAAABTQLuACIBTQAAAAMCPQFWAAAAAQAc//YBvQIcADUAAAAnJiYjIgYVFBYXFxYWFRQGBiMiJicmNjc3NhYXHgIzMjY1NCYnJyYmNTQ2NjMyFhcWBgcHAZcDGFE2QVMrNoc6OjVcOUh0GQIBAxYCBgESNEUpRFw4PmdGOTJXNj5kHQIBAhcBpQUkKDkvHC4RLBNAKypDJj0yAwUCDgIBAh4rFzouIjATIBU/KS1EJTAsAwMCDwD//wAc//YBvQL0ACIBVAAAAAMCMgFyAAD//wAc//YBvQLmACIBVAAAAAMCNgF2AAD//wAc/zQBvQIcACIBVAAAAAMCQQFFAAD//wAc//YBvQLmACIBVAAAAAMCNQF2AAD//wAc/xQBvQIcACIBVAAAAAMCQAEcAAD//wAc/1sBvQIcACIBVAAAAAMCPwEWAAAAAQAe//YCDgLmAFEAABI2MzIWFRQGBgcGBhUUFhceAhUUBgYjIiYnJiY3NzYyFxYWMzI2NTQmJicuAjU0Njc+AjU0JiMiBhURFgYjIyI1ETQjIyI1NTQ2MzMyNjVpcVxRRx8iMDMfHzA1RTozVTMjTBgEAgIQAQYDGi8gSFEpPjAmJx8uOCUeGj83R1UBBAUYCAg+BgMDPwQCAnltTzYlMBUaHBsYGBYPESBIOjVMJxEPAgQEGAICDg5DPCs2Hg8MESYhJDEcExIlHCsxVlP98wUFCAHdCgYXAgQFAwAAAQAaAAABRALGADAAAAAGIyMiFREUFjMzMhYVFRQHBiMiJjURNCYjIyI1NTQzMzI1NzQzMzIWFRUUMzMyFRUBRAIEiwceIE8EBAYRPTsuAwRdBgZeBgkIDgUEB4sGAfIDB/6UNCUDBBEFAgQ2QQFxBAMGFwYHowoFBqIHBhcAAgAaAAABRALGAA4APwAAABYVFRQGIyMiJjU1NDMzNgYjIyIVERQWMzMyFhUVFAcGIyImNRE0JiMjIjU1NDMzMjU3NDMzMhYVFRQzMzIVFQERAwUF1AQDBds1AgSLBx4gTwQEBhE9Oy4DBF0GBl4GCQgOBQQHiwYBOwMDFQQEAwUUB7cDB/6UNCUDBBEFAgQ2QQFxBAMGFwYHowoFBqIHBhf//wAaAAABVQLzACIBXAAAAAcCNAFfAA3//wAa/zQBRALGACIBXAAAAAMCQQE6AAD//wAa/xQBRALGACIBXAAAAAMCQAERAAD//wAa/1sBRALGACIBXAAAAAMCPwDoAAAAAQBT//YB4AISACUAADYWMzI2NxE0NjMzMhYVERQjIyI1NTQmBwYGIyImNRE0MzMyFhURe0I5MV00BQQWBQQIFQgEBDxWKkddCRYFBGRINTQBhAUEBAX+AAkIQAUCBDAlUkEBfwoEBf6W//8AU//2AeAC9AAiAWIAAAADAjIBlAAA//8AU//2AeAC5gAiAWIAAAADAjcBmAAA//8AU//2AeAC5gAiAWIAAAADAjUBmAAA//8ASf/2AeAC8AAiAWIAAAADAjwBjAAA//8AU//2AeAC5gAiAWIAAAADAi8BZgAA//8AU/9bAeACEgAiAWIAAAADAj8BMAAA//8AU//2AeAC8AAiAWIAAAADAjEBmwAA//8AU//2AeAC8wAiAWIAAAADAjsCKwAA//8AU//2AkYCTwAiAWIAAAADAj4CEgAAAAMAU//2AkYC9AAlADMAQwAANhYzMjY3ETQ2MzMyFhURFCMjIjU1NCYHBgYjIiY1ETQzMzIWFREBMhYHBgYjNzI2NzY2MyYWFxcWBgcHBiYnJyY2Nzd7QjkxXTQFBBYFBAgVCAQEPFYqR10JFgUEAcIGAwILPTUFHyAIAQYDoAUBCgIDBuMFBQEFAQIC5WRINTQBhAUEBAX+AAkIQAUCBDAlUkEBfwoEBf6WAbAFBiM3KSAYAwGlAgIgBQUCSgIDBA8EBQFbAAMAU/9bAkYCTwAlADMAQwAANhYzMjY3ETQ2MzMyFhURFCMjIjU1NCYHBgYjIiY1ETQzMzIWFREBMhYHBgYjNzI2NzY2MwIWFRUUBiMjIiY1NTQ2MzN7QjkxXTQFBBYFBAgVCAQEPFYqR10JFgUEAcIGAwILPTUFHyAIAQYD+wQECB0HBAQFImRINTQBhAUEBAX+AAkIQAUCBDAlUkEBfwoEBf6WAbAFBiM3KSAYAwH9cwQGUQgEBAZTBgQAAwBT//YCRgLwACUAMwBDAAA2FjMyNjcRNDYzMzIWFREUIyMiNTU0JgcGBiMiJjURNDMzMhYVEQEyFgcGBiM3MjY3NjYzJTY2FxcWFgcHBgYnJyYmN3tCOTFdNAUEFgUECBUIBAQ8VipHXQkWBQQBwgYDAgs9NQUfIAgBBgP+gwEFAuUCAgEFAQUF4wYDAmRINTQBhAUEBAX+AAkIQAUCBDAlUkEBfwoEBf6WAbAFBiM3KSAYAwGdAgIBWwEFBA8EAwJKAgUFAAADAFP/9gJGAvMAJQAzAFAAADYWMzI2NxE0NjMzMhYVERQjIyI1NTQmBwYGIyImNRE0MzMyFhURATIWBwYGIzcyNjc2NjMkBgcGBhcXFjc2MzIWFRQGBwYUFxcWNzY2NTQmI3tCOTFdNAUEFgUECBUIBAQ8VipHXQkWBQQBwgYDAgs9NQUfIAgBBgP+1CUPAgEBCgIDHxMUFhcUAgEOBAIbHScgZEg1NAGEBQQEBf4ACQhABQIEMCVSQQF/CgQF/pYBsAUGIzcpIBgDAaQNCQEEAhcEAhQPEgwgFAIEAREDAhsrFR4jAAMAU//2AkYC2wAlADMAVgAANhYzMjY3ETQ2MzMyFhURFCMjIjU1NCYHBgYjIiY1ETQzMzIWFREBMhYHBgYjNzI2NzY2MyYGBwYGIyImJiMiBgYHBiYnJyY2NzY2MzIWFjMyNjc2FhcXe0I5MV00BQQWBQQIFQgEBDxWKkddCRYFBAHCBgMCCz01BR8gCAEGA44CBA4dGBYnJBEOExAHBQQCBwEBAhMdFBIqKRIPFA8FBgIIZEg1NAGEBQQEBf4ACQhABQIEMCVSQQF/CgQF/pYBsAUGIzcpIBgDAWsEAgkKDAwGCgQCAQUQAwQBDQ0NDAcJAwEFEgD//wBT//YB4ALwACIBYgAAAAMCMwHtAAD//wBT//YB4ALuACIBYgAAAAMCPQGXAAD//wBT//YB4ALSACIBYgAAAAMCOgGUAAD//wBT/1kB/AISACIBYgAAAAcCQgIGAAH//wBT//YB4AMfACIBYgAAAAMCOAF6AAD//wBT//YB4ALbACIBYgAAAAMCOQGZAAAAAQAfAAAB8wISABkAAAEyFgcDBgYjIyImJwMmNjMzMhYXExY3EzYzAeoEBQLOAgkHDggKAs4CBQUWBQYBtwcJtwMIAhIFBP4CBgUFBQH+BQUDBf46ExQBxgcAAQAjAAAC6wISADIAAAEyFgcDBgYjIyImJwMmIgcDBgYjIyImJwMmNjMzMhYXExY2NxM2NjMzMhYXExY2NxM2MwLiBAUBkwEJBxsICQGMAwUCggEIBx4ICQGcAgUFFwUFAosDCAOKAQYDFwUGAZIDBwOFAwoCEgUE/gIGBQUFAbQICP5NBgUFBQH+BQUDBf45CgEKAcgEAgMF/jkKAQoBxwf//wAjAAAC6wL0ACIBeAAAAAMCMgIUAAD//wAjAAAC6wLmACIBeAAAAAMCNQIYAAD//wAjAAAC6wLmACIBeAAAAAMCLwHmAAD//wAjAAAC6wLwACIBeAAAAAMCMQIbAAAAAQAeAAAB4wISAC0AACUWBiMjIicnJiIHBwYjIyImNzc2JicnJjYzMzIWFxcWMjc3NjYzMzIWBwcGFhcB3gUBBiIJA6UEBgStAwkZBwQGwgQBA7YCAwQfBAUCogMHBJ4CBgQbBAICtgQBAwwGBgXgBQXgBQYI9QQIBfUDBgID1wUF1wMCBQPxBAkEAAABAB//WwHdAhIAIwAAFgYGIyImNTU0NjMzMjY2JwMmNjMzMhYXExY2NxM2MzMyFgcD6Sg0ISYaCxI8HjEUCr0CAwcVBQYBrgQKBKoDBxYEBQLlbScRAwcPBwQ7UBsB4QUHBAT+RAkBDAG5BwYD/ar//wAf/1sB3QL0ACIBfgAAAAMCMgGHAAD//wAf/1sB3QLmACIBfgAAAAMCNQGLAAD//wAf/1sB3QLmACIBfgAAAAMCLwFZAAD//wAf/1sB3QISACIBfgAAAAMCPwFtAAD//wAf/1sB3QLwACIBfgAAAAMCMQGOAAD//wAf/1sB3QLzACIBfgAAAAMCOwIeAAD//wAf/1sB3QLbACIBfgAAAAMCOQGMAAAAAQAkAAABpAISACYAADMiJjU1NDY3ATYmIyEiJjU1NDYzITIWFRUUBgcBBhYzITIVFRQGIyoEAgQEAUEDAgT+1wQDAwMBWAIDAwT+vwMCAwFHBgMFAwQXBQoFAbUEBAMEFQQDAwMZBgsE/kwEAwYVBAT//wAkAAABpAL0ACIBhgAAAAMCMgF8AAD//wAkAAABpALmACIBhgAAAAMCNgGAAAD//wAkAAABpALmACIBhgAAAAMCMAEXAAD//wAk/1sBpAISACIBhgAAAAMCPwEFAAD//wAaAAAB9ALmACIBAQAAAAMBCwFsAAD//wAaAAAB7wLmACIBAQAAAAMBIAFsAAAAAgAwAZUBRQLtAC0APAAAEjYzMhYVFRQWFxYGIyMiJicnJiIHBgYjIiY1NDY3NzY1NTQjIgYHBgYnJyY0NxYGFRQWMzI3NjU1NCYHB0xGLTtBAwYBAwMZBQMBBgEDBBs0ITQ7UE89C1whLBMBBAMYAQFdQSwlLiMeBAYyAsglPTaiERMRBAQCBCYEAxsYMCswNQgGAgcSTRYcAgEBCgEEAm4nIhshIBsZMQYEAQQAAgA4AZcBawLvAA8AGwAAEjY2MzIWFhUUBgYjIiYmNSQmIyIGFRQWMzI2NTglRi4vRiUkRTEwRSQBCjs2Njo6Njc6AnVPKytPNDJNKytNMkFKSj5BS0tBAAEAHgAAAnQCEgArAAAABiMjIhURFCMjIiY1AzQjIyIVFAIHBiMjIiY3NjY1NCMjIjU1NDMhMhYVFQJ0BQN1CgoWBQMBCv4KIRgDBw8FBQEYHwldCgsCQwQEAfQFCv4mCwYHAdkJCY/++kkIBgVc+YUKCREJBQUPAAIAT//2AlQC8AAPAB0AABI2NjMyFhYVFAYGIyImJjUeAjMyNjY1NCYjIgYVT0R1Skp1Q0N1S0t0QzA2Xz8/XjRyYGBzAe2tVlWtfX2qVFSpfHGWSEeXc6ypqqsAAQA3AAABhwLmACYAACUyFRUUBiMhIjU1NDMzMjY1ETQmBwcGNTU0Njc3NjYzMzIWFREUMwGABwQD/r4GBo0CBAMDjAgDA4kGCgYbBAQFKgYeAgQGHgYCAgJ4BAMBOAMKGAQDAkQDAwUE/VMGAAABADkAAAH5AvAAMQAANjY3Nz4CNTQmJiMiBgcGBicnJjQ3NjYzMhYWFRQGBgcHBhYzITIWFRUUBiMhIiY1NTkGCOc1QCAyTyoyaCADCAUPBgQne0A3YDsiSELUBQMIAXkFBAQE/lQHBSsOCO82U04pMEQiMC8EAQQNBQUGNjcqWUMtUVpE2AUHAwUaBAQFBxQAAAEAMf/2AigC8ABBAAATJjY3NjYzMhYWFRQGBwYUFxYWFRQGBiMiJicmNDc3NhYXFhYzMjY1NCYmIyMiNTU0NjMzMjY2NTQmJiMiBgcGIidUAwECMmY+RWk5Rj4LC0JZPXlWR200AwQVBAQDK2M+ZnM/ZThaCQUESTtiOS9TNTZgJgQHBQKFAwcCMi0xUzQ6WBYEBAMRZEk6XzgsMwMDBBQEAQMtJlpKN08oBxoDBClILShBJSkmBAQAAAIANAAAAj4C5gAmADIAAAE2NjMzMhYVERQzMzIVFRQjIyIVFRQGIyMiJjU1NCMhIiY1NTQ2NxYWMyEyNjURNCYHAQGRAgcFHwUEB2YKCmUIBQUcBAQI/q4GBQQFKQMIARsEBQcH/uMC4QIDBAX+KwgHGQoJxAUEBQTHBgQGHwUKBw4HBAMBgwsCCf5/AAABADz/9gI+AuYAOwAAEjYzITIWBwcUBiMhIhUDBhY3NjYzMhYWFRQGBiMiJicmNjc3NhcWFjMyNjY1NCYjIgYHBiYnJyYmNjUTewMKAY0GBAEDAQf+nAkVAQcEME03QGxDR31OQ3kwBAEEEwQGM1Q/SWs4blI5XDIGBAQWBAMCGwLkAgQFGgQDCP7tBgcDHB0uY0xOczwvLwQEBBQEBiwoNWA/W1snIAQCAw4DAgUGAV4AAgBP//YCRgLwACQANgAAEjY2MzIWFhcWBwcGJyYmIyIGFRQWNzY2MzIWFhUUBgYjIiYmNQQmJiMiBgcGBhUUFhYzMjY2NU9EglsmPDAWBwcYBgcYPyt8eAUKOVs7S2o2QG1CUnc/AcksVDkvdCUOCjFjRzVXMgHkr10SIBgHBBEFCRwcrI0SCAYdHjhiPkRpOVend2BQLx4XChMUN3pTMlYzAAABAC8AAAIYAuYAHQAAEjYzITIWFRUUBgcGAgYHBiMjIiY3NhI3NiMhIjU3MwQFAdUEAwMFQ39bDQIJHQYFAh6cbgkP/l0FAwLjAwMDGAcKB2L++vRGDgcGggF3qgwHHAADAEn/9gJOAvAAIAAvAD0AABI2NjMyFhYVFAYHBhcWFhUUBgYjIiY1NDY3NjQnLgI1JCYjIgYGFRQWFxcWNjY1ABYzMjY1NCYnJyYGBhVdPm1DRGY4MDgODUFJQXZOeIg3Ow4OHioWAaJiUjZYMj4+dRQ/MP54b2RidDs+mBVKOQJcXjYyXD0xTisKBBRdQDtaMWhbN1cpCQkHDy06I09VK0suN0QNGQQxUir+R1FWSThDDSIFL1QxAAACAEH/9gIyAvAAJAA2AAAABgYjIiYmJyY3NzYXFhYzMjY1NCYHBgYjIiYmNTQ2NjMyFhYVJBYWMzI2NzY2NTQmJiMiBgYVAjJDf1koQTIXBwcYBgcYRTB3dgUKN1k7SWo2QG1CUHQ+/j0sUzcxciQOCjBhRjZWMgECr10SIBgHBBEFCRsdrI0SCAYcHzhiPkRpOVend2FQMCIaChMUNnZRMVYz//8AP/9QAYQBLwAHAaQAIv9b//8AKf9bAPwBLgAHAaUAIv9b//8ANv9bAVABNAAHAaYAIv9b//8AO/9SAXcBMQAHAacAIv9b//8ANv9bAX4BLgAHAagAIf9b//8AP/9SAYIBKwAHAakAI/9b//8AP/9QAXsBLwAHAaoAI/9b//8AQP9bAXMBLgAHAasAIf9b//8AQf9SAYYBMQAHAawAIv9c//8AO/9QAXMBLwAHAa0AI/9bAAIAHf/1AWIB1AAPAB0AABI2NjMyFhYVFAYGIyImJjUeAjMyNjY1NCYjIgYVHSpKLy5KKipKLy9JKh4iPCcoOyFIPD1IATFtNjVtT09qNTVqTkdfLS1fSGxra2wAAQAHAAAA2gHTACYAADcyFRUUBiMjIjU1NDMzMjY1ETQmBwcGNTU0Njc3NjYzMzIWFREUM9YEAwHLBARZAQIBAlgFAQJXAwcDEQMCBBoDEwEDBBMDAgEBjQMCASMCBhACAgErAgIDA/5RBAAAAQAUAAABLgHZADEAADY2Nzc+AjU0JiYjIgYHBiInJyY0NzY2MzIWFhUUBgYHBwYWMzMyFhUVFAYjISImNTUUBAWRISkUIDEbH0IUAgUDCQQCGU0pIj0lFi0qhQMCBe0DAwMC/vIEAxsJBZYiNDEaHisWHx0DAggEAwMiIxo4KxwzOCuIAwUBBBACAwMFDAAAAQAZ//cBVQHWAEEAABMmNjc2NjMyFhYVFAYHBhQXFhYVFAYGIyImJyY0Nzc2FhcWFjMyNjU0JiYjIyI1NTQ2MzMyNjY1NCYmIyIGBwYiJy8CAQEfQCcsQiQsJwcHKTgmTDYtRSACAg4CAwEcPidASCdAIzkFAwIuJT4kHjQhIj0YAgQEAZMCBAEgHB80ISQ4DQMCAgs/LiQ8IxsgAgIDDAMBAhwYOS4jMhkEEAIDGi0cGSkYGhgDAwAAAgAVAAABXQHTACYAMgAAEzY2MzMyFhURFDMzMhUVFCMjIhUVFAYjIyImNTU0IyMiJjU1NDY3FhYzMzI2NTU0JgcH8AEFAxQDAgVABgZABQMDEgIDBdQEAwIDGgIFsgMDBQSzAdABAgMD/tkFBBAGBnsDAwMDfQQCBBMEBgQJBAMB9AcBBvIAAAEAHP/3AV8B0AA7AAASNjMzMhYHBxQGIyMiFQcUFjc2NjMyFhYVFAYGIyImJyY0Nzc2FxYWMzI2NjU0JiMiBgcGJicnJiY2NTdDAgf5BAMBAgEE4AYNBAIfMCMoRCotTjEqTR4CAwwCBCA1KC5DI0U0JDkgBAIDDQMCAREBzgIDAxADAgWtAwUCEhIdPjAxSSUdHgIDAg0CAxwZIT0nOToZFAIBAggCAgME3AACABz/9QFYAdQAJAA2AAASNjYzMhYWFxYHBwYnJiYjIgYVFBY3NjYzMhYWFRQGBiMiJiY1BCYmIyIGBwYGFRQWFjMyNjY1HCpSORgmHg4FBQ8EBA8oG05LAwYkOSUvQyIoRSk0SycBHxw0JB5JFwkGHz4tITcfAStvOgsUEAQCCwMFEhJtWAwFBBITIz4nKkIkNmlLPDIeEw8GDAwjTTQgNiAAAAEAHwAAAVIB0wAdAAASNjMhMhYVFRQGBw4CBwYjIyImNzY2NzYjISI1NyEDAwEnAgICAypQOQgCBRIEAwETYkUGCv75AwEB0QICAg8FBgQ+pZksCQQEUuxrBwURAAADAB//9gFkAdUAIAAvAD0AABI2NjMyFhYVFAYHBhcWFhUUBgYjIiY1NDY3NjQnLgI1JCYjIgYGFRQWFxcWNjY1AhYzMjY1NCYnJyYGBhUrJ0UqK0AjHiMJCCkuKUoxTFUjJQgIExsOAQc9NCI3ICcnSg0nHvZGPz1JJSdgDS4kAXg7Ih86Jx4yGwYCDTopJTgfQTkjNxkGBgQKHCQWMjYbMB0iKwgQAh40Gv7rMzYuIysIFQMdNR8AAgAY//UBUAHUACQANgAAJAYGIyImJicmNzc2FxYWMzI2NTQmBwYGIyImJjU0NjYzMhYWFSQWFjMyNjc2NjU0JiYjIgYGFQFQKlA4GSkfDgUFDwMFDyseS0sEBiI4Ji1DIihFKTNJJv7lGzUiH0gWCQcfPSwiNh+dbjoLFA8FAgsDBhESbFkLBQMSFCQ9JytCJDdpSz0yHhURBgwMIkszHzYgAAACAB0BEQFiAvAADwAdAAASNjYzMhYWFRQGBiMiJiY1HgIzMjY2NTQmIyIGFR0qSi8uSioqSi8vSSoeIjwnKDshSDw9SAJNbTY1bU9PajU1ak5HXy0tX0hsa2tsAAEABwEUANoC5wAmAAATMhUVFAYjIyI1NTQzMzI2NRE0JgcHBjU1NDY3NzY2MzMyFhURFDPWBAMBywQEWQECAQJYBQECVwMHAxEDAgQBLgMTAQMEEwMCAQGNAwIBIwIGEAICASsCAgMD/lEEAAEAFAEXAS4C8AAxAAASNjc3PgI1NCYmIyIGBwYiJycmNDc2NjMyFhYVFAYGBwcGFjMzMhYVFRQGIyEiJjU1FAQFkSEpFCAxGx9CFAIFAwkEAhlNKSI9JRYtKoUDAgXtAwMDAv7yBAMBMgkFliI0MRoeKxYfHQMCCAQDAyIjGjgrHDM4K4gDBQEEEAIDAwUMAAEAGQERAVUC8ABBAAATJjY3NjYzMhYWFRQGBwYUFxYWFRQGBiMiJicmNDc3NhYXFhYzMjY1NCYmIyMiNTU0NjMzMjY2NTQmJiMiBgcGIicvAgEBH0AnLEIkLCcHByk4Jkw2LUUgAgIOAgMBHD4nQEgnQCM5BQMCLiU+JB40ISI9GAIEBAKtAgQBIBwfNCEkOA0DAgILPy4kPCMbIAICAwwDAQIcGDkuIzIZBBACAxotHBkpGBoYAwMAAAIAFQEUAV0C5wAmADIAABM2NjMzMhYVERQzMzIVFRQjIyIVFRQGIyMiJjU1NCMjIiY1NTQ2NxYWMzMyNjU1NCYHB/ABBQMUAwIFQAYGQAUDAxICAwXUBAMCAxoCBbIDAwUEswLkAQIDA/7ZBQQQBgZ7AwMDA30EAgQTBAYECQQDAfQHAQbyAAABABwBDgFfAucAOwAAEjYzMzIWBwcUBiMjIhUHFBY3NjYzMhYWFRQGBiMiJicmNDc3NhcWFjMyNjY1NCYjIgYHBiYnJyYmNjU3QwIH+QQDAQIBBOAGDQQCHzAjKEQqLU4xKk0eAgMMAgQgNSguQyNFNCQ5IAQCAw0DAgERAuUCAwMQAwIFrQMFAhISHT4wMUklHR4CAwINAgMcGSE9Jzk6GRQCAQIIAgIDBNwAAgAcARIBWALxACQANgAAEjY2MzIWFhcWBwcGJyYmIyIGFRQWNzY2MzIWFhUUBgYjIiYmNQQmJiMiBgcGBhUUFhYzMjY2NRwqUjkYJh4OBQUPBAQPKBtOSwMGJDklL0MiKEUpNEsnAR8cNCQeSRcJBh8+LSE3HwJIbzoLFBAEAgsDBRISbVgMBQQSEyM+JypCJDZpSzwyHhMPBgwMI000IDYgAAABAB8BEwFSAuYAHQAAEjYzITIWFRUUBgcOAgcGIyMiJjc2Njc2IyEiNTchAwMBJwICAgMqUDkIAgUSBAMBE2JFBgr++QMBAuQCAgIPBQYEPqWZLAkEBFLsawcFEQAAAwAfAREBZALwACAALwA9AAASNjYzMhYWFRQGBwYXFhYVFAYGIyImNTQ2NzY0Jy4CNSQmIyIGBhUUFhcXFjY2NQIWMzI2NTQmJycmBgYVKydFKitAIx4jCQgpLilKMUxVIyUICBMbDgEHPTQiNyAnJ0oNJx72Rj89SSUnYA0uJAKTOyIfOiceMhsGAg06KSU4H0E5IzcZBgYEChwkFjI2GzAdIisIEAIeNBr+6zM2LiMrCBUDHTUfAAIAGAESAVAC8QAkADYAAAAGBiMiJiYnJjc3NhcWFjMyNjU0JgcGBiMiJiY1NDY2MzIWFhUkFhYzMjY3NjY1NCYmIyIGBhUBUCpQOBkpHw4FBQ8DBQ8rHktLBAYiOCYtQyIoRSkzSSb+5Rs1Ih9IFgkHHz0sIjYfAbpuOgsUDwUCCwMGERJsWQsFAxIUJD0nK0IkN2lLPTIeFREGDAwiSzMfNiD//wA/AXkBhANYAAYBriJo//8AKQF8APwDTwAGAa8iaP//ADYBgAFQA1kABgGwImn//wA7AXoBdwNZAAYBsSJp//8ANgF8AX4DTwAGAbIhaP//AD8BdgGCA08ABgGzI2j//wA/AXsBewNaAAYBtCNp//8AQAF8AXMDTwAGAbUhaf//AEEBegGGA1kABgG2Imn//wA7AXsBcwNaAAYBtyNpAAH/cf/8AYUC7gAPAAAAFgcBBgYjIyImNwE2NjMzAYIDA/4XAwYGEAcCBAHpAgYEFALuBAT9HgUDCAUC4AMCAP//ACX//ANTAu4AIgGvHgAAIwHCATUAAAADAaYCJQAA//8AL//3A3MC7gAiAa8oAAAjAcIBRQAAAAMBpwIeAAD//wA8//cDigLwACIBsCgAACMBwgFvAAAAAwGnAjUAAP//AC///AMpAu4AIgGvKAAAIwHCAT8AAAADAagBzAAA//8ALf/8A1MC8AAiAbEUAAAjAcIBewAAAAMBqAH2AAD//wA5//YDfgLuACIBrzIAACMBwgE2AAAAAwGsAhoAAP//AC3/9gOWAvAAIgGxFAAAIwHCAWwAAAADAawCMgAA//8AMP/2A6QC7gAiAbMUAAAjAcIBdQAAAAMBrAJAAAD//wAp//YDWALuACIBtQoAACMBwgEdAAAAAwGsAfQAAAABAB0BxgE1AuYAOAAAEiInJyYiBwcGIicnJjQ3NzYmJycmJjc3NhcXFjY1JzQ2MzMyFhUHFDc3NhcXFgYHBwYGFxcWBgcH+AUBRwEBAUsBBQIWAgJRAQEDbQIBAQgCBWgCAwcEAx4DAgUEawUBBwEBAmwCAQFOAgEDFQHKAnABAXUBAQ8CBAJqAQMBIAEDAxYFAyYBAgN3AgMDAncGAi0CBBUDBAEmAQMCYwMFAg8AAAEAD//EATgC5gAOAAAFIiYnAyY2MzMyFhcTFiMBFAQGAfgCAwQdAwQB+wIHPAQEAxAFBQMD/OkFAAABAC0BTgB/AZ0ADQAAEhUVFAYjIyImNTU0MzN/Bgc7BgQHRAGdCDwGBQUFPAkAAQBLATEA2wHBAA8AABIGBiMiJiY1NDY2MzIWFhXbEyEUFCETEyEUFCETAWUhExMhFBQhExMhFAACADwAAwB6AfQADgAdAAASFRUUBiMjIiY1NTQ2MzMSFRUUBiMjIiY1NTQ2MzN6BgcnBgQEAzAHBgcnBgQEAzAB9Ag8BgUFBTwFBP5eCDwGBQUFPAUEAAEALf96AH8ATwAYAAAXIiY3NzYjIyImNTU0NjMzMhUVFAcHBgYjMgMCAisEDAkEBAMDMgYCOQIFBIYFBXIKBANCAwMGOQYGhAMDAP//ADwAAAJGAE8AIwHWAcwAAAAiAdYAAAADAdYA5gAAAAIAVQAAAJMC5gAOAB4AADYVFRQGIyMiJjU1NDYzMxAWFQMUBiMjIiY1AzQ2MzOTBgcnBgQEAzADCwUFDgQECgMCKk8IPAYFBQU8BQQClwQC/bwEBAMEAkQEAwAAAgBV/1sAkwISAA4AHgAAExQjIyImNTU0NjMzMhYVAxQGIyMiJjUTNDYzMzIWFZMHMAMEBAYnBwYEAwMqAgMKBAQOBQUBywgEBTwFBQUG/VoCBAMEAhUEAwQEAAAEAC3//wIuAucADgAdACwAOwAAAAcDBgYnJyYmNRM2NhcXFgcDBgYnJyYmNRM2NhcXEhYVFRQGIyEiJjU1NDMhNhYVFRQGIyEiJjU1NDMhARoBcAEEAxgDA3ABBAIY0wFwAQQDGAMDcAEEAhgsBAUF/jIEBAUB1CQEBQX+MgQEBQHUAuEK/TAEBAEEAQMCAtYDBAEEAQr9MAQEAQQBAwIC1gMEAQT+PgMDGAQEAwQYB9IDAxgEBAMEGAcAAAEAPAAAAHoATwAOAAA2FRUUBiMjIiY1NTQ2MzN6BgcnBgQEAzBPCDwGBQUFPAUEAAL+Kf0cAdwC8AANADAAACQVFRQGIyMiJjU1NDMzAyYmNzY2MzIWFhUUAAAVFAYjIyImNTQGEjU0JiMiBgcGBicBEwYHJwUFBzDgAwICIHlHQF80/E0C4AQDGwQEEuFaSzpsEQEEA08IPAYFBQU8CQIeAQUCOEMtUjdK+ywDzkwDBQUEUxkBHkNATDspAgIBAAIAHv9RAdMCEgANADAAABI1NTQ2MzMyFhUVFCMjExYWBwYGIyImJjU0AAY1NDYzMzIWFRQGBhUUFjMyNjc2NhfnBgcnBQUHMOADAgIgeUdAXzQBEj8EAxsEBBu0Wks6bBEBBAMBwwg8BgUFBTwJ/hEBBQI4Qy1SN0UBH0hRAwUFBFchtj5ATDspAgIBAP//AFACJgEjAuYAIwHaAIgAAAACAdoAAAABAFACJgCbAuYADwAAEhYVBwYGIyMiJicnNDYzM5gDFAEEBRIEAwETBAI/AuYDA7EFBAMEswMDAAIALf96AH8B9AAYACcAABciJjc3NiMjIiY1NTQ2MzMyFRUUBwcGBiMTIiY1NTQ2MzMyFRUUBiMyAwICKwQMCQQEAwMyBgI5AgUEDQYEBAMwBwYHhgUFcgoEA0IDAwY5BgaEAwMCKwUFPAUECDwGBQABAA//xAE4AuYADgAAFyI3EzY2MzMyFgcDBgYjFgcC+wEEAx0EAwL4AQYEPAUDFwMDBQX88AQEAAEARv+aAa//wwAOAAAEFhUVFAYjBSImNTU0MyUBqwQFBf6pBAQFAV09AwMaBAQBAwQaBwEAAAEAAP9bAPwC5gA2AAATNDY2MzMyFRUUIyMiBgYVFRQGBwYGFxYWFRUUFjMzMhUVFAYjIyImJjU1NCYmIyI1NTQzNjY1dBgwJBUHBxUXHA01KQYBByk1HCUTCAMFEyYxFhozIwQENjoCjBsoFwUaBgsXE+k6QQQBBQEDPTvrHRoIFQUDGCkb6xkyIAYbBgE+MAAAAQAP/1sBCwLmADYAABIWFhUVFBYXMhUVFCMiBgYVFRQGBiMjIiY1NTQzMzI2NTU0Njc2JicmJjU1NCYmIyMiNTU0MzNPMBg6NgQEIzMaFjEmEwUDCBMlHDUpBwEGKTUNHBcVBwcVAuYXKBvpMD4BBhsGIDIZ6xspGAMFFQgaHes7PQMBBQEEQTrpExcLBhoFAAABAGr/WwD9AuYAGwAAEzQ2MzMyFRUUIyMiFQMUFjMzMhUVFAYjIyImNWoEBIQHB1kHAQIEWQgDBYAEBQLgAwMFGgYI/M0EAggVBQMEBAABAA//WwCiAuYAGwAAEhYVAxQGIyMiJjU1NDMzMjY1AzQjIyI1NTQzM54EAQUEgAUDCFkEAgEHWQcHhALmAwP8gwQEAwUVCAIEAzMIBhoFAAEAUP9bAPkC5gAYAAATMhYHBgYVFBYXFgYjIyImJyYmNTQ2NzYz8gQDAjZANTYCAwQKAwQCQkJNQgUHAuYFAmrocW/YcwIFAgNx0np36WMGAAABABP/WwC8AuYAGAAAEjYzMzIXFhYVFAYHBgYjIyImNzY2NTQmJxMDBAcHBUJNQkICBAMKBAMCNjVANgLhBQZj6Xd60nEDAgUCc9hvcehqAAEARgDvAxgBGAAOAAAAFhUVFAYjBSImNTU0MyUDFAQFBf1ABAQFAsYBGAMDGgQEAQMEGgcBAAEARgDvAdcBGAAOAAAAFhUVFAYjBSImNTU0MyUB0wQFBf6BBAQFAYUBGAMDGgQEAQMEGgcBAAEARgD1ATcBHgAOAAAAFhUVFAYjByImNTU0MzcBMwQFBd8EBAXlAR4DAxoEBAEDBBoHAf//AEYA9QE3AR4AAgHmAAAAAgAgAE4BdgG9ABMAJwAAEhcXFiMjIicnJjc3NjYzMzIWBwcGFxcWIyMiJycmNzc2NjMzMhYHB/wEcgQLDAYCigYFjAIDAg4DBAJyqwRyBAsMBgKKBgWMAgMCDgMEAnIBAAWoBQSoBwayAwEFA64HBagFBKgHBrIDAQUDrgAAAgAwAE4BhQG9ABMAJwAANzYnJyY2MzMyFhcXFgcHBiMjIjclNicnJjYzMzIWFxcWBwcGIyMiN6QEBHICBAMOAgMCjAUGigIGDAkCARkEBHICBAMOAgMCjAYHigIGDAkC+wYGrgMFAQOyBgeoBAWoBgauAwUBA7IGB6gEBQAAAQAgAE4AzwG9ABMAABIXFxYjIyInJyY3NzY2MzMyFgcHVQRyBAsMBgKKBgWMAgMCDgMEAnIBAAWoBQSoBwayAwEFA64AAAEAMQBOAN4BvQATAAA3NicnJjYzMzIWFxcWBwcGIyMiN6UFBXICBAMOAgMCjAUGigIGDAkC+wYGrgMFAQOyBQioBAX//wAw/44BKABSACMB8QCSAAAAAgHxAAAAAgA8AiIBNALmABgAMQAAEyI1NTQ3NzY2MzMyFgcHBhYzMzIWFRUUIyMiNTU0Nzc2NjMzMhYHBwYWMzMyFhUVFCPUBgJCAQYEEQMDAS0DAgURBAQG2wYCQgEGBBEDAwEtAwIFEQQEBgIiBj4CBHUDAgUCXQcHBANFBgY+AgR1AwIFAl0HBwQDRQYAAgAwAiIBKALmABgAMQAAEyImNzc2JiMjIiY1NTQzMzIVFRQHBwYGIyMiJjc3NiYjIyImNTU0MzMyFRUUBwcGBiPIAwMBLQMCBREEBAZJBgJCAQYEowMDAS0DAgURBAQGSQYCQgEGBAIiBQJdBwcEA0UGBj4CBHUDAgUCXQcHBANFBgY+AgR1AwIAAQA8AiIAogLmABgAABMyFgcHBhYzMzIWFRUUIyMiNTU0Nzc2NjOcAwMBLQMCBREEBAZJBgJCAQYEAuYFAl0HBwQDRQYGPgIEdQMCAAEAMAIiAJYC5gAYAAATIiY3NzYmIyMiJjU1NDMzMhUVFAcHBgYjNgMDAS0DAgURBAQGSQYCQgEGBAIiBQJdBwcEA0UGBj4CBHUDAgABADD/jgCWAFIAGAAAFyImNzc2JiMjIiY1NTQzMzIVFRQHBwYGIzYDAwEtAwIFEQQEBkkGAkIBBgRyBQJdBwcEA0UGBj4CBHUDAgAAAwA4AAAB9gLmAA8AHwBLAAAlFAYjIyImNTU0NjMzMhYVERQGIyMiJjU1NDYzMzIWFQI2NjMyFhYXFgcHBiYnJiYjIgYGFRQWFjMyNjY3NjYXFxYWBw4CIyImJjUBMQYHEAcFBQQYBAQGBxAHBQUEGAQE+T1tRitIORgFBRgCBgMpSCs9XDIyXDwiOzMWAwQDFwIBARg/SytHbTwNBwYGBmQFBAQEAgkHBgYGZAUEBAT+6H5HGTAiBwMQAgIEMSo7bEZGbDsWLCIFAQIPAgUDJzQaRHxRAAUAOQAAAfcC5gAOAB0ALAA7AGcAAAEGJycmJjc3NjYzMzIWBxcGJycmJjc3NjYzMzIWBwMGBiMjIiY3NzYXFxYWBwcGBiMjIiY3NzYXFxYWBwI2NjMyFhYXFgcHBiYnJiYjIgYGFRQWFjMyNjY3NjYXFxYWBw4CIyImJjUBIgMNDwcEAhwBBgQZBAMBQwMNDwcEAhwBBgQZBAMBdAEGBBkEAwEeAw0PBwQCfQEGBBkEAwEeAw0PBwQCrz1tRitIORgFBRgCBgMpSCs9XDIyXDwiOzMWAwQDFwIBARg/SytHbTwCbQwDBAIHBmkEAgYEbwwDBAIHBmkEAgYE/SoEAgYEbwwDBAIHBmkEAgYEbwwDBAIHBgFPfkcZMCIHAxACAgQxKjtsRkZsOxYsIgUBAg8CBQMnNBpEfFEAAgBHALcBvgI0AEEAUQAAJCMiJyYHBwYiJycmNDc3NicmJjU0NzYmJycmNDc3NhcXFhY3NjMyFxY3NzYXFxYHBwYXFhUUBwYXFxYHBwYnJyYHJhYWMzI2NjU0JiYjIgYGFQE2NzcrBwUoBQkFCwQEKAYGDg4fAgECKAQECwkIKQIGBCs2NioIBS4HBhEFBS8FBB8eBQYuBgYRBgUtCAXeITkjIzkhITkjIzkhzB8FBikEBAwFCAQoBwkUMRo2LAQGAigFCQULCQkpAgECHh8EBS4HBxEGBi8FBiw1NikIBi0HBhEGBi0HBGc5IyM5ISE5IyM5IQAAAwAk/6MCFgM2AFMAYABsAAAAFhUUBgYjIgYVFRQGIyMiJjU1NCYjIiYnJjc3NjIXFhYzMjY1ETQmJy4CNTQ2NzI2NTU0NjMzMhYVFRQWMxYWFxYWBwcGJicmJicmBhURFBYXFyY2NRE0JgcGBhUUFhcTFDMyNjU0JicmBhUBwFY0XDsEBAMEGAQDAgNXbygGCBAECgIoXEYEAwwLTVEldV0DBAMDGAQEAwQ7XCgDAQQXBAQFGFEwBQQEBxtXCwYER1dAVDsHRlVDTggJAU1iRjJPLgMDRQQEAwJIAwM8PwoFCgIDNjIDBAEjCQwGIzZBL1FpBwQDOAMEBQU2AwQFMCoDCAMRAwIGHikGAQQF/tIHBQMMIgYJARoEBAEGUz0sRCb+jwZNOzVIIQMDCAAEAC0AAAHdAuUAIwAxAD4ATAAAJSI1NTQmBw4CIyImJjU0NjYzMhYXFjY1NTQzMzIWFREUBiMDJiYjIgYVFBYzMjY2NRIVFRQjIyImNTU0MzMCFRUUBiMhIiY1NTQzIQGBCAUFExU3JjVWMjFaOys0GgcECBgDAwQFIBw+JEVWV0AlOyJpCfEEAwX3NgQF/q8EAwUBV4YJKgcCBRQUFzFiRkVqOhkXBgMIyAgDBP2yBQUBWBwdZVhYXSEzGAGNBhYHAwQWBv2FBhYEAwMEFgYAA//v//YCDwLwACYANQBEAAAkBiMiJiY1NDY2MzIWFxYHBwYGJyYmIyIGFRQWMzI2NzYWFxcWFgcCFhUVFAYjBSImNTU0MyUWFhUVFAYjBSImNTU0MyUB7E0qYIhHRoZdJ0wiBwYSAgcDGjwgeoCCgCM+GgEEARMBAQKpBAUF/pkEBAUBbQMEBQX+mQQEBQFtDRdYqnh4rVsYFgQIGAMBAhMWrqejrBISAQEBGwIEAQG3AwMYBAQBAwQYBwGgAwMYBAQBAwQYBwEAAAH/jv9WAW4C5gA6AAABMhYVBwYjIyIHAwYGIyMiJjc3NjYzNzY2NxM2JiMjIjc3NDYzMzI2Nzc2NjMzMgcHBgYjIyIGBwcGMwFAAgIEAQeFBwFcC0I7MQQEAQMBAwM0KCkIXQEDA0oFAQQEA0kEAwEOCkU4SAUBAwEFBEkgLQcPAQcCEgMDFwYH/e8/QgUEEwMDAQEoMAIWAwQGFwMDAwRRPT8HFQQDLiZWBwACAAYAAAIjAuYADgA0AAAkFhUVFAYjBSImNTU0MyUDNDYzITIVFRQGIyEiBhURFBYzITIVFRQGIyEiBhURFAYjIyImNQFJBAUF/ssEBAUBO9wEBQGpBwMF/oYFBAQFAV0HAwX+pAUEAwQeBQS+AwMYBAQBAwQYBwECHgYEBxoEAwMG/tAEAwcaBAMDBv66BAMEBgAAAQAMAAACGgLwAGwAACUWBwYGIyEiNTU0MzMWNjU0JyYjIyImNTU0MzMyNicnJiMjIiY1NTQzMzI2JycmNjYzMhYXFgYHBwYnJiYjIgYVFBcWMzcyFhUVFAYjByIXFxYWMzcyFhUVFAYjIyIGFxYGBwYWMzMyNjc2NhcCEwcCFl1D/rIIByMqOAIBB2gEBAVnAgQBEgEHTwQEBU0CAwEDESpgRDxmDwEEAxcIAxJCM0RbDgIIsgMEBQWpCAERAQQEmQMEBQWQAwUBBRkgAwEE0zhHEgEHAnEEBDU0CBgHAVpWByQGAwQYBwMDZQUDBBgHAgISZH02OSQDBAELAwYgIkxCGFQIAQMDGAQEAQlfBQMBAwMYBAQEAzl1JAQDJCkDAwEAAAMAEQAAAeIC5gAcACsAOgAAJAYjIyImNRE0NjMzMhYVERQWMzMyNjc2FxcWFgcCFhcXFgYHBQYmJycmNyUWFhcXFgYHBQYmJycmNyUBwIxdZAUEBAUcBQQEBTVQbCUCBCICAQGzBAIJAgQH/vUFBQIJAwYBEwQEAgkCBAf+9QUFAgkDBgETbGwFBQLSBgQEBv1VBgNXYwMBCgEDAwEzAwMZBQcDYQECBRkIA2SmAwMZBQcDYQECBRkIA2QAAwAGAAACvwLmAA4AHQBDAAAAFhUVFAYjBSImNTU0MyUWFhUVFAYjBSImNTU0MyUCMzMyFhURFAYjIyImJwEmBhURFAYjIyI1ETQ2MzMyFhcBFjY1EQK7BAUF/VkEBAUCrQMEBQX9WQQEBQKtYwgbBQQEBQ8GCgP+VAUHBQUZCQQFEgYGAwGrBQkB4wMDGAQEAQMEGAcBoAMDGAQEAQMEGAcBAaMEBv0uBQUBBQJ3BwEG/YwFBAoC0gYEAwT9igcEBQJyAAMAAAAAAgwC5wAoADcARgAAETQzMzI2NTQjIyImNTU0NjMzMhYVFAYHBhcTFgYjIyInAyYmIyMiJjUAFhUVFAYjBSImNTU0MyUWFhUVFAYjBSImNTU0MyUHrG5otMsFBQQFzW13V0EGAoICBQUdCQN/AgkKzwcFAggEBQX+CAQEBQH+AwQFBf4IBAQFAf4BjwZKVYoFBRQGBFpXUlgUAQb+ngUJCQFaBQUDBAFzAwMYBAQBAwQYBwGgAwMYBAQBAwQYBwEAAAEADAAAAhoC8ABPAAAzIjU1NDMzFjY1NCcmIyMiNTU0MzMyJycmNjYzMhYXFgYHBwYnJiYjIgYVFBYXFjMzMhUVFCMgMiMiFxYVFAYHBhYzMzI2NzY2FxcWBwYGIxQIByMqOA0BBF0FBVQIAhIVK2ZBPGYPAQQDFwgDEkIzRFsSCgIEqwYG/uWABggBDSMTAwEE0zhHEgEHAhYHAhZdQwgYBwFaVjBbBQYaBgZHUn9GOSQDBAELAwYgIkxCKF0fBgUbBgc+OklaFgQDJCkDAwEJBAQ1NAADABMAAAPWAuYADgAdAE4AAAAWFRUUBiMFIiY1NTQzJRYWFRUUBiMFIiY1NTQzJQMyFgcDBgYjIyInAyYiBwMGBiMjIiYnAyY2MzMyFhcTFjY3EzYzMzIWFxMWNjcTNjMD0gQFBfxPBAQFA7cDBAUF/E8EBAUDtw8EBAHPAgcHIA8DtQECAbkCBwcgCAkBygIFBRkGBAK5AwYDvQILGQYEArkDBgO9AQsB4wMDGAQEAQMEGAcBoAMDGAQEAQMEGAcBAaMFBP0uBgUKAogDA/15BgUFBQLSBQUDBf1pCgEKApcHAwX9aQoBCgKXBwAAAwATAAACLwLmACMAMgBBAAABMhYHAwYGFREUBiMjIiY1ETQmJwMmNjMzMhYXExYWNxM2NjMCFhUVFAYjBSImNTU0MyUWFhUVFAYjBSImNTU0MyUCJgUEA+0DAQcIEwYGAgPyAwQHGwcGA9EFBwXVBAUHOQQFBf6kBAQFAWIDBAUF/qQEBAUBYgLmBwT+cwQJB/7TBwYGBQEgBgkFAZsFBwQF/poJAQkBZwYD/msDAxgEBAEDBBgHAYwDAxgEBAEDBBgHAQAAAgBGAKwB1wI9AA4AHQAAABYVFRQGIwUiJjU1NDMlBgYjIyImNQM0NjMzMhUTAdMEBQX+gQQEBQGFrgMDGAQEAQMEGAcBAYgDAxgEBAEDBBgHAdgEBQUBfwQEBf57AAEARgFhAdcBiAAOAAAAFhUVFAYjBSImNTU0MyUB0wQFBf6BBAQFAYUBiAMDGAQEAQMEGAcBAAIARgC2AcECMQAPAB0AACQWBwcGIicBJjQ3NzY2FwEEJycmNDcBNjIXFxYHAQG+AQIRAwYE/qoDAxECBAIBWv6jBBEDBAFVAwUDEQUE/qfUBQIRAwQBVQMFAxECAQL+pyAEEQMGBAFWAwMRBAT+pgADAEYArgHXAjsADQAcACoAACQVFRQGIyMiJjU1NDMzNhYVFRQGIwUiJjU1NDMlJhUVFAYjIyImNTU0MzMBOAYHOwYEB0SiBAUF/oEEBAUBhZgGBzsGBAdE/Qg8BgUFBTwJiwMDGAQEAQMEGAcBswg8BgUFBTwJAAACAEYA6QHXAewADgAdAAAAFhUVFAYjBSImNTU0MyUWFhUVFAYjBSImNTU0MyUB0wQFBf6BBAQFAYUDBAUF/oEEBAUBhQHsAwMYBAQBAwQYBwHcAwMYBAQBAwQYBwEAAwBGAFYB1wJ0AA4AHQAsAAAAFgcDBgYjIyImNxM2MzMWFhUVFAYjBSImNTU0MyUWFhUVFAYjBSImNTU0MyUBjQIC6gIFBBcEAgHvAwUVSwQFBf6BBAQFAYUDBAUF/oEEBAUBhQJ0BQX99AQEAwICEwaIAwMYBAQBAwQYBwHcAwMYBAQBAwQYBwEAAQBGALUBzwIZABYAADc0NjclJSYmNTU0NhcFFhUVFAcFBiY1RwYHAT7+wQcGCAkBcAgI/pAHCdgFBgOAfwMGBBoIBQSUAwcjBwOSAwYIAAABADwAtQHFAhkAFgAAJBYVFRQGJyUmNTU0NyU2FhUVFAYHBQUBvgYJB/6QCAgBcAkIBgf+wQE+4wYFFQgGA5IDByMHA5QEBQgaBAYDf4AAAAIARgBTAdcCbAAWACUAABM0NjclJSYmNTU0NhcFFhUVFAcFBiY1BzQ2MyUyFRUUBiMFIiY1RwYHAT7+wQcGCAkBcAgI/pAHCQEDAgGFBwUF/oEEBAErBQYDgH8DBgQaCAUElAMHIwcDkgMGCKQEAwEGGAQEAQMEAAIAPABTAc0CbAAWACUAAAAWFRUUBiclJjU1NDclNhYVFRQGBwUFFhYVFRQGIyUiJjU1NDMFAcYGCQf+kAgIAXAJCAYH/sEBPgsDBAT+gQUFBwGFATYGBRUIBgOSAwcjBwOUBAUIGgQGA3+AwAMEGAQDAQQEGAYBAAMARgBTAdcCgwAOAB0ALAAAJBYVFRQGIwUiJjU1NDMlEhYVFRQGIwUiJjU1NDMlBgYjIyImNQM0NjMzMhURAdMEBQX+gQQEBQGFAwQFBf6BBAQFAYWvAwMXBAQBAwQYB3oDAxgEBAEDBBgHAQFUAwMYBAQBAwQYBwHYBAUFAX8EBAX+ewAAAgBBANQB3QHzACEAQwAAAAYHBgYjIiYmIyIGBwYmJycmNDc2NjMyFhYzMjY3NhYXFxYGBwYGIyIWJiMiBgcGJicnJjQ3NjYzMhYWMzI2NzYWFxcB3QEDDDYoHz4+HiIwEgQDAgcBAhkyJB5BQR4hJxEEBQIIAQEDDDYoIi2nHSIwEgQDAgcBAhkyJB5BQR4hJxEEBQIIAc8FAw4XFhYVEQQBBRMDBQEVGBcWExAEAQUVwgUDDhcPOxURBAEFEwMFARUYFxYTEAQBBRUAAQBBAUAB3QGgACEAAAAGBwYGIyIWJiMiBgcGJicnJjQ3NjYzMhYWMzI2NzYWFxcB3QEDDDYoIi2nHSIwEgQDAgcBAhkyJB5BQR4hJxEEBQIIAXwFAw4XDzsVEQQBBRMDBQEVGBcWExAEAQUVAAACAEYA1gHXAYgADgAcAAAAFhUVFAYjBSImNTU0MyUWFRUUBiMjIiY1JzQzMwHTBAUF/oEEBAUBhQcDBBcEAwEGGAGIAwMYBAQBAwQYBwEHCpkEBAMCnwcAAwBGAKMB+wI7AA8AHwAuAAAABgYjIiYmNTQ2NjMyFhYVBBYWMzI2NjU0JiYjIgYGFSQUBwEGBicnJjY3ATYXFwHnNls2Nls1NVs2Nls2/pcrSywsSywsSywsSysBfQL+agIGAREDAQQBkgcEEQE/WzY2WzY2WzU1WzYsSywsSywsSiwsSiysBAL+iwIBAhIDBgQBcQYGEgADADIAfAJ+AaoAJQA1AEMAAAAWFhUVFAYGIyImJicmIgcGNgYjIiYmNTQ2NjMyFhYXFjI3NjYzBBYzMzI2NzYmJyYmIyIGFSQmIyMiBgcGFxYzMjY1AhpAJChEKB8vIxkFBQY3BzolJ0EnJ0YsHy4iGQgHBiA8Lv5rOisDIkEXCgELHTskLDgB9TUrASM4IhISPkQqMgGqJEIsAS9HJRUhHQYGOgYfJkUsLUQmEx4bBwckKMQ9MCAPDhEsLTsvKz8sKRUWV0IqAAEACf9RAaEDWAAvAAA2JiY1NDY2MzIWFxYWBwcGBicmJiMiBhUUFhYVFAYGIyImJyYmNzc2NhcWFjMyNjXXGxomQCYlJxsHBQIIAQoGGSIWLDobGiY+JCUnGwcFAggBCgYZIhYpOVX9/WE1TCcIDgMJBhcFAgMMCTs5YP/+YDVMJwgOAwkGFwUCAwwJOzkAAAEARwAAArcC8ABCAAAAJiYjIgYGFRQWFhcWFhUVFCMjIiY1NTQ2MzMyNicuAjU0NjYzMhYWFRQGNgcGFjMzMhUVFAYjIyI1NTQ2Nz4CNQKGOndWVnc6Rlc2BQYJ2QMFBQOVBwQGdAJRSY5hYY5JUkm+BgQHlQgFA9kJBgU3VkYB4oxXV4xPWolTKAQJChQKBAQbBAMGBWMCl2dcnV9fnVtmmESqBQYHGwQEChQKCQQoVIlZAAIAKAAAAlUC5wAUAB4AADImNTU0NxM2MxcyFxMWFhUVFAYjITYzITInAyYiBwMtBQf0AwkgCQTyBAMEBv3nIBEBvAwF4AQGBOAFBg8WFAKZCgEL/WwMEggVBgYqDgJmCwv9mgABAA4AAAJlAuYALwAAATIWFRUUBiMjIgYVERQGIyMiJjURNCYjISIGFREUBiMjIiY1ETQmIyMiJjU1NDYzAl4EAwMFXgUEBAUcBQQEBf71BQQEBRwFBAQFXgUDAwQC5gMEHAQDAwb9VwYEBAYCqQYDAwb9VwYEBAYCqQYDAwQcBAMAAQAZAAAB3ALmACYAACQGIyEiNTU0NxM2JwMmNTU0MyEyFRUUIyEiFBcTFgcDBhQzITIVFQHcBQb+UAgC2wUHygILAY8HBf6jAwHIAwLXAgMBegoFBQkSBwMBRggLAUEDBhQKBhwGBAP+wgYD/sADBQgUAAABABP/WwJZA04AHAAAABUBBiMjIicDJgcHBicnJjY3NzYXExYWNxM2MzMCWf78AwwYDQWvAwo2CwQGAgMEZQsDpQIFAvUCBhsDTgb8HwwNAdkJBBUEDA8EBwEoBQr+LAYBBwOlBgACADf/9gIUAvAAJAA3AAASBgcGIicnJjc2NjMyFhYVFAYjIiYmNTQ2NjMyFhcWNjU0JiYjAhYWMzMyNjY3NiYnJiYjIgYGFdk8JgQFAgoEBxplM1BxPIJ2QWk7PHBNM0ExBwU0YkadLk8uATRZOgcCAwUabDU1VS8CxBgYAgQQBwccIFSofLnJPWpCQWA0FxwEAQRtj0b95VUzP3JLEQ4FHiotTzIAAQBT/1sB4AISAC4AADYWFjMyNjcRNDYzMzIWFREUIyMiNTU0JgcGBiMiJicVFAYjIyImNRE0MzMyFhUReyI5IS9fMwUEFgUECBUIBAQ9USwnPhkECBEHBAkWBQRsMh42MwGEBQQEBf4ACQhBBQIEMCYWGb4IBAQGAqMKBAX+gAAFAEH/9gNBAvEADwAbACsAOwBLAAASNjYzMhYWFRQGBiMiJiY1FhYzMjY1NCYjIgYVADY2MzIWFhUUBgYjIiYmNR4CMzI2NjU0JiYjIgYGFRIWBwEGBiMjIiY3ATY2MzNBKUoxMEkpKUkwMUopI0Y6OkZGOjpGAZcpSjEwSSkpSTAxSikjITolJTohITolJTohrwMD/hcDBgYQBwIEAekCBgQUAmFfMTFfREVgMTFgRVFnZ1BQZ2dQ/vRfMTFfREVgMTFgRTRTMTBUMzNUMDFTMwIjBAT9HgUDCAUC4AMCAAcAQf/2BM4C8QAPABsAKwA7AEsAWwBrAAASNjYzMhYWFRQGBiMiJiY1FhYzMjY1NCYjIgYVADY2MzIWFhUUBgYjIiYmNR4CMzI2NjU0JiYjIgYGFSQ2NjMyFhYVFAYGIyImJjUeAjMyNjY1NCYmIyIGBhUCFgcBBgYjIyImNwE2NjMzQSlKMTBJKSlJMDFKKSNGOjpGRjo6RgGXKUoxMEkpKUkwMUopIyE6JSU6ISE6JSU6IQFqKUoxMEkpKUkwMUopIyE6JSU6ISE6JSU6Id4DA/4XAwYGEAcCBAHpAgYEFAJhXzExX0RFYDExYEVRZ2dQUGdnUP70XzExX0RFYDExYEU0UzEwVDMzVDAxUzNFXzExX0RFYDExYEU0UzEwVDMzVDAxUzMCIwQE/R4FAwgFAuADAgACACgAAAGMAuYAFQAlAAA2BiMjIicDJjQ3EzY2MzMyFxMWFAcDAyYGBwMGFBcTFjI3EzYmJ/MFBCUGApMCApgBBQQjBwKTAQGXEAQKA3cCAngDCQN3AQEBAwMFAWYEBgQBZwMDBf6cBAkE/poCoQoBCf7XAwcE/s4IBwEuBAgEAAIANf+PAx0ClQBLAFgAAAQHBgYjIiYmNTQ2NjMyFhYVFAYGIyImJyYmBwYGIyImJjU0NjYzMhYXFjY3NzYzMzIHBwYWMzI2NjU0JiYjIgYGFRQWFjMyNjc2FxcABhUUFjMyNjc2JiYjApgOL2w6da5dZbVyWZ9kM1c1JikGAQUFGUUxIz4lMVY1KD4SAQQBCgEGHQgCJggWICZBJViJSmedVk6dcDpHMAwHDP7XTTApNlULCRo0HiQKISJkqmhst21GkWpLdkIZIAcBBiInLFA0QGo+JiICAgMmBQblMSk1YD1fgD1hpWJZlVkdHwgJEAHJbE8/RlpBNkolAAMAKP/7AtAC8QBAAFEAYQAAEjY2MzIWFhUUBgcGBhcXFjI3NjY3NjYXFxYWBwYGBwYWFxYWMzIWFRUUBiMiJicmIgcGBiMiJjU0Njc2NicmJjUkJiMiBgYVFBYXFxYWNzY2NQcmJgcGBhUUFjMyNjc2Nid2M1o3LkwsW2UHAQW8BQMHKmQNAQgEEQMEAQ5jNQQBBR5xMgcFBgoxiScEBgQvbjxfcUNOCgEKIyEBPEQ0LkMkFhsXAgoFYVO9BgwKPkZeSTFiJQcBBQJsVDEjQS08aCwDBgfgBQYmkCcEAwIHAgUDLo41BAYFHi0FBQ8IBjYhAwQsL2lXPWMwBgkKJEgoXTUoQygfNR0ZAwECLlU0+QcDBR1hOUNTKiIGCwYAAAEAOP+rAcIC5gAnAAAAIyMiFREUIyMiJjURNCMjIgYVERQjIyI1ETQjIiYmNTQ2NjMzMhUVAcIENAQEGQMCBSMCAgUZBQNEYzM0aUueBALGBfzvBQMCAxIEAwL87wUFAX0DO2A3OmlBBBkAAAIALv+8AbAC8ABAAFAAAAAGBxYWFRQGBiMiJicmNDc3NjIXFhYzMjY1NCYnJyYmNTQ2NyYmNTQ2NjMyFhcWBwcGIicmJiMiBhUUFhcXFhYVBxY2NjU0JicnDgIVFBYXAbA2LjEzLlM1PnEcAQEYAwUBGls4PEs1QWI7Nz4xQS4tUTQ4XRwDBBQEBgIaRypBSis6WklCoB01ISg5QSA5Iik6ARM7FRw/KCU8Iz0zAgUBDwEDKjMxKyE3GiYXRS0tRhEgQSkkOyI0KAUDDQICIicvKB4zGCUfTjJmAhkvGio2FxoDIC8ZIi4YAAMAIv/2AxwC8AAPAB8ASQAAAAYGIyImJjU0NjYzMhYWFQQWFjMyNjY1NCYmIyIGBhU+AjMyFhYXFgYVBwYmJyYmIyIGFRQWMzI2NzYyFxcWFgcOAiMiJiY1Axxnr2dor2Zmr2hnr2f9M1uaW1maXV2aWVuaW5YwVjYiOC4UAgIcAgQCGDshQVNSQShCFAEHAhQCAQETMjshN1YvAQuvZmavaGevZ2evZ2SZVFSaY2KaVlaaYkFjNxcoHAIEAREBAgMkJGJOTmMrJAMCDAEFAh8sGDZhPwAEACIBEwH/AvAADwAfAEAATgAAAAYGIyImJjU0NjYzMhYWFQQWFjMyNjY1NCYmIyIGBhUFFgYjIyInJzQmIyMiFRUUIyMiNTU0MzMyFhUUBgciBhcnMjY1NCYjIyImFRUUMwH/QG1BQW5AQG5BQG5A/kczXDw7XDM0Wzs8XDMBHAECAhgCAikCBTgEAxcDAmQkJhwVAQEBHxcbFx47AgIDAcBtQEBtQUFuQEBuQT1cMjJcPT1cMzNcPXMCAwNsAQIEagQD5gQeHBscBwEBGw8SEBIBBD0DAAAEACL/9gMcAvAADwAfADQAQwAAAAYGIyImJjU0NjYzMhYWFQQWFjMyNjY1NCYmIyIGBhUkFhUUBiMjIiIVFRQjIyI1ETQ2MzMWNjU0JiMjIiYGFRUUMzMDHGevZ2ivZmavaGevZ/0zW5pbWZpdXZpZW5pbAaBNTT9jAgIEIAQCA4cuODcwXgEDAQRjAQuvZmavaGevZ2evZ2SZVFSaY2KaVlaaYsUzMz5BBaEFBAGGAwPBKScrIgUDBpMGAAIAHQEWA0oC5gAfAFEAABImNTU0NjMhMhYVFRQGIyMiBhURFAYjIyImNRE0JiMjASImNRE0NjMzMhYXExYyNxM2MzMyFhURFAYjIyImNRE0BhUDBgYjIyInAyYmFREUBiMfAgIDAUICAgIDhQQCAwMRBAIDA5MBlwMDAwMQBAMBpwMFAqoBBRMDAgIDEQMEAZwBBQQRCQOYAQEDAwLNAgIQAwICAxACAgIE/lUDAwMDAasEAv5JAwMBxAMDAgP+XQYHAaIFAwP+PAMDAwMBggIBAf5/BAMGAX8BAQL+gQMDAAACAFICLgF+A1oADwAbAAAAFhYVFAYGIyImJjU0NjYzEjY1NCYjIgYXFhYzARNEJydEKytEJydEKzA7OzIxPAEBOjIDWiZFKytFJiZFKytFJv74PjMyPj4yMz4AAAEAcv9bAJkDTwAOAAAWBiMjIiY1AzQ2MzMyFRGZAwQYBAMBAwMZCKEEAwID6AMECvweAAACAHL/WwCZA08ADgAdAAASBiMjIiY1AzQ2MzMyFREQBiMjIiY1AzQ2MzMyFRGZAwQYBAMBAwMZCAMEGAQDAQMDGQgBvwQDAgGIAwQK/n79nAQDAgGIAwQK/n4AAAIAHP/2AZEC8AAxAD4AACQ2FxcWFAcOAiMiJiYXJgcHBgYnJyY3NzY2NRE0NjMyFhUUBgYHBhUVFBYWMzI2NjcCBhUVFBY3NjY1NCYjAXEFBBQDAhcjNihHPBEHAQUrBQQCDgQGQgQCVkE6Q0RdRQYUKiQhLB0RpjcICUVvLSp2AgINAgQCJSocUZcfBQMfAwEEFgcELwIGBQEVbHBUQ0SDaUMGBQ1ETCAWIhwCVGFyyAkHCD6oWzAyAAEAAP9bAZQC5gAvAAABMhYVFRQGIyciBhUTFAYjIyImNRM0JiMHIiY1NTQ2MxcyNjUDNDYzMzIWFQMUFjMBjQQDAwOrBAMKAwIrAwMKBAWrAwMDBKkFAgcDAysCAwcDBAHSAwIlAwMHAwT9vwMDAwQCQAQDBwMDJQIDBQMFAQoEAwMD/vUFAwABAAD/WwGUAuYATwAAEyIGFREUFjM3MhYVFRQGIyciBhUTFAYjIyImNRM0JiMHIiY1NTQ2MxcyNjURNCYjByImNTU0NjMXMjY1AzQ2MzMyFhUDFBYzNzIWFRUUBiPjBAMDBKsDAwMEpwQDBwMCKwMDBwIFqQQDAwOrBQQEBasDAwMEqQUCBwMDKwIDBwMEpwQDAwMBqQME/v0EAwcDAyUCAwUDBf71AwMDBAEKBQMFAwIlAwMHAwQBAwQDBwMDJQIDBQMFAQoEAwMD/vUFAwUDAiUDAwAAAgAi//YDHALwACMAMgAAJTIWBwYGIyImJjU0NjYzMhYWFRQGIyEiFREUFhcWMzI2NzYzAiMiBwYVFRQzITI1NTQnAtEDAQI4oFxor2Zmr2hmr2gGB/3dCAIESmtMkC4DBLRdZE4JBwFeBwiYAwNHVWavaGevZ2SoYwgGCP7sBgUCNUo2AwI1LwYM7wcI9wkFAP//AHIAAAQSAu8AIgBhAAAAAwGOAqcAAAABADsAiQGiAiAAFwAAJCYnAyYiBwMGBiMjIiY3EzY2FhcTFiMjAXYHAXwCBgJ9AQUEHwQDAo4JGBkKkAMJHokEBAE9BQX+xAQDBgQBYxcREBj+mgkAAAIAJAEQA0AC7AA0AGYAAAAmJyYmIyIGFRQXFxYWFRQGBiMiJicmNjc3NjIXFhYzMjY1NCYnJyYmNTQ2NjMyFhcWFgcHEyImNRE0NjMzMhYXExYyNxM2MzMyFhURFAYjIyImNRE0BhUDBgYjIyInAyYmFREUBiMBPgIDFDslND5MVjYvJUUvNEscAgIEBwQGAhw5LztBIjFeLyckQisqQRwCAQMNbgMDAwMQBAMBpwMFAqoBBRMDAgIDEQMEAZwBBQQRCQOYAQEDAwKZAQQYHDIuOxsfFDkoJjYcJSgDBgMFAwMkICwxHigSJRE6IyY3HR8dAgUCDP57AwMBxAMDAgP+XQYHAaIFAwP+PAMDAwMBggIBAf5/BAMGAX8BAQL+gQMDAAAC/1UChP/2AuYADwAfAAACFhUVFAYjIyImNTU0NjMzMhYVFRQGIyMiJjU1NDYzM30EBAgbBwQEBSB0BAQIGwcEBAUgAuYEBkwHBQQGTgYEBAZMBwUEBk4GBAAB/8QChP/2AuYADwAAAhYVFRQGIyMiJjU1NDYzMw4EBAgbBwQEBSAC5gQGTAcFBAZOBgQAAf75AnT/9gLwAA8AAAM2NhcXFhYHBwYGJycmJjf7AQUC5QICAQUBBQXjBgMCAuwCAgFbAQUEDwQDAkoCBQUAAAH+/gJ4//sC9AAPAAACFhcXFgYHBwYmJycmNjc3FwUBCgIDBuMFBQEFAQIC5QL0AgIgBQUCSgIDBA8EBQFbAAAC/rwCS//jAvAADgAdAAACFxcWBgcHBiInJyY2Nzc2FxcWBgcHBiInJyY2NzfHBBYDAQV8BAYCCQMBAnWSBBYDAQV8BAYCCQMBAnUC8AUbBAUEdQMEDQMFAoUFBRsEBQR1AwQNAwUChQAAAf+xAjf/9gLmABkAAAMiJjc3NiYnJyYmNTU0MzMyFRUUBhUHBgYjSQMDAR4CAQQFBQMGKwUBIQIFBgI3BQJGBQMBAQEDBEkHBlIBAgFMBAMAAAH+8QKE//YC5gAbAAACJicnJiIHBwYGIyMiJjc3NjYzMzIWFxcWFCMjPQgDOwcECD4EBwQlBQIFYwQKBhIFCANiBQYpAoQDAzAGBjADAwYETwQFBQNQBQUAAAH+8QKE//YC5gAbAAADNjYzMzIUBwcGBiMjIiYnJyY2MzMyFhcXFjI3SAMIBCkGBWIDCAUSBgoEYwUCBSUEBwQ+CAQHAuADAwUFUAMFBQRPBAYDAzAGBgAB/vMCiv/3AuYAGgAAAzIWBwYGIyImJyY2MzMyFhceAjMyNjc2NjMSBgMCCEU2ND8LAQEEIwQFAwUSHhYfJgsCBAMC5gQFHjUzIQIGBAMIFQ8bEgMDAAL/LgJZ//YDHwALABcAAAIWFRQGIyImNTQ2MxY2NTQmIyIGFRQWM0Q6OioqOjspFyIhGRgjJBcDHzkpKzk7Kyg4niMYGiEhGhckAAAB/vACof/2AtsAIgAAAgYHBgYjIiYmIyIGBgcGJicnJjY3NjYzMhYWMzI2NzYWFxcKAgQOHRgWJyQRDhMQBwUEAgcBAQITHRQSKikSDxQPBQYCCAK6BAIJCgwMBgoEAgEFEAMEAQ0NDQwHCQMBBRIAAf76Aqz/9gLSAA4AAAIWFRUUBiMjIiY1NTQzMw0DBAbrBAMF8gLSBAIYBAQEBBcHAAAB/o8CVf8bAvMAHAAAAAYHBgYXFxY3NjMyFhUUBgcGFBcXFjc2NjU0JiP+xiUPAgEBCgIDHxMUFhcUAgEOBAIbHScgAvMNCQEEAhcEAhQPEgwgFAIEAREDAhsrFR4jAAAC/r0CS//jAvAADgAdAAABNhcXFhYHBwYiJycmJjc3NhcXFhYHBwYiJycmJjf+1gUEdQIBAwkCBgR8BQEDogUEdQIBAwkCBgR8BQEDAusFBYUCBQMNBAN1BAUEGwUFhQIFAw0EA3UEBQQAAf7zApL/9wLuABoAAAIGIyMiJicmJiMiBgYHBgYjIyImNzY2MzIWFwkDBiMDBAILJh8WHhIFAwUEIwQBAQs/NDZFCAKWBAMDEhsPFQgDBAYCITM1HgAB/7UB6gA0Ak8ADQAAEzIWBwYGIzcyNjc2NjMrBgMCCz01BR8gCAEGAwJPBQYjNykgGAMBAAAB/8L/W//2/8IADwAABhYVFRQGIyMiJjU1NDYzMw4EBAgdBwQEBSI+BAZRCAQEBlMGBAAAAf+2/xT/9v+uABkAAAciJjc3NiYjIyImNTU0MzMyFRUUBhUHBgYjRQMCARkCAgQIBQMHLQYBHwIGBuwFAzYEBQQFQggHTAIDATsEAgAAAf9F/zT/9gAFAB0AAAYWFRQGIyInJjc3NhcWFjMyNjU0JiciJjc3MwcGMy0jOSsqHwQBCQIFCxkUHx0lKAMCASQiGAEEPigbJCcVAgQUBAMJCRYVGBYCAwFUPAMAAAH/R/9Y//YACwAZAAAGJjU0NjcXBgYVFBYzMjY3NhYVFRQGBwYGI4suMj0iNi8cHhUkCAQEAgMNKxaoKiQjMREJGSkcFRUIBAIBBBQDBAEGCQABAB0B3QBjAuYAGQAAEyImNzc2JiMjIiY1NTQzMzIVFRQGFQcGBiMjAwMBHwEBAwUEBAYrBQEiAQYGAd0FAngGBAQEcQcGegECAX4EA///AA4CeAELAvQAAwIyARAAAP//AAoCigEOAuYAAwI3ARcAAP//AAkChAEOAuYAAwI2ARgAAP//AAn/NAC6AAUAAwJBAMQAAP//AAkChAEOAuYAAwI1ARgAAP//AAoChACrAuYAAwIvALUAAP//AAoChAA8AuYAAgIwRgD//wAJAnQBBgLwAAMCMQEQAAD//wAdAksBRALwAAMCMwFhAAD//wAKAqwBBgLSAAMCOgEQAAD//wAK/1gAuQALAAMCQgDDAAD//wAKAlkA0gMfAAMCOADcAAD//wAKAqEBEALbAAMCOQEaAAD///5GAor/SgOmACMCN/9TAAAABwIy/00Asv///kcCiv9LA6IAIwI3/1QAAAAHAjH/VQCy///+TAKK/1ADpQAjAjf/WQAAAAcCO//3ALL///5VAor/XQONACMCN/9mAAAABwI5/2UAsv///kkChP9OA3oAIwI1/1gAAAAHAjL/UwCG///+UQKE/1gDdgAjAjX/YAAAAAcCMf9iAIb///5bAoT/mgN2ACMCNf9qAAAABwI7AH8Ag////l0ChP9jA2EAIwI1/20AAAAHAjn/bQCGAAAAAAAoAeYAAQAAAAAAAAA2AAAAAQAAAAAAAQAOADYAAQAAAAAAAgAHAEQAAQAAAAAAAwAgAEsAAQAAAAAABAAOADYAAQAAAAAABQANAGsAAQAAAAAABgAVAHgAAQAAAAAABwAwAI0AAQAAAAAACAAOAL0AAQAAAAAACQAxAMsAAQAAAAAACgBeAPwAAQAAAAAACwAZAVoAAQAAAAAADAAZAVoAAQAAAAAADQCQAXMAAQAAAAAADgAaAgMAAwABBAkAAABsAh0AAwABBAkAAQAcAokAAwABBAkAAgAOAqUAAwABBAkAAwBAArMAAwABBAkABAAcAokAAwABBAkABQAaAvMAAwABBAkABgAqAw0AAwABBAkABwBgAzcAAwABBAkACAAcA5cAAwABBAkACQBiA7MAAwABBAkACgC8BBUAAwABBAkACwAyBNEAAwABBAkADAAyBNEAAwABBAkADQEgBQMAAwABBAkADgA0BiMAAwABBAkBAAAMBlcAAwABBAkBAQAIBmMAAwABBAkBAgAUBmsAAwABBAkBAwAKBn8AAwABBAkBBAAOAqUAAwABBAkBBQAMBokAAwABBAkBBgAQBpUAAwABBAkBBwAIBqUAAwABBAkBCAASBq0AAwABBAkBCQAKBr9Db3B5cmlnaHQgKGMpIDIwMTUsIEltcGFsbGFyaSBUeXBlICh3d3cuaW1wYWxsYXJpLmNvbSlMaWJyZSBGcmFua2xpblJlZ3VsYXIxLjAxNDtJTVBBO0xpYnJlRnJhbmtsaW4tUmVndWxhclZlcnNpb24gMS4wMTRMaWJyZUZyYW5rbGluLVJlZ3VsYXJMaWJyZSBGcmFua2xpbiBpcyBhIHRyYWRlbWFyayBvZiBJbXBhbGxhcmkgVHlwZS5JbXBhbGxhcmkgVHlwZVBhYmxvIEltcGFsbGFyaSwgUm9kcmlnbyBGdWVuemFsaWRhLCBOaHVuZyBOZ3V5ZW5MaWJyZSBGcmFua2xpbiBpcyBhIHJlaW50ZXJwcmV0YXRpb24gYW5kIGV4cGFuc2lvbiBvZiB0aGUgMTkxMiBNb3JyaXMgRnVsbGVyIEJlbnRvbtVzIGNsYXNzaWMuaHR0cDovL3d3dy5pbXBhbGxhcmkuY29tL1RoaXMgRm9udCBTb2Z0d2FyZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgU0lMIE9wZW4gRm9udCBMaWNlbnNlLCBWZXJzaW9uIDEuMS4gVGhpcyBsaWNlbnNlIGlzIGF2YWlsYWJsZSB3aXRoIGEgRkFRIGF0OiBodHRwOi8vc2NyaXB0cy5zaWwub3JnL09GTGh0dHA6Ly9zY3JpcHRzLnNpbC5vcmcvT0ZMAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABjACkAIAAyADAAMQA1ACwAIABJAG0AcABhAGwAbABhAHIAaQAgAFQAeQBwAGUAIAAoAHcAdwB3AC4AaQBtAHAAYQBsAGwAYQByAGkALgBjAG8AbQApAEwAaQBiAHIAZQAgAEYAcgBhAG4AawBsAGkAbgBSAGUAZwB1AGwAYQByADEALgAwADEANAA7AEkATQBQAEEAOwBMAGkAYgByAGUARgByAGEAbgBrAGwAaQBuAC0AUgBlAGcAdQBsAGEAcgBWAGUAcgBzAGkAbwBuACAAMQAuADAAMQA0AEwAaQBiAHIAZQBGAHIAYQBuAGsAbABpAG4ALQBSAGUAZwB1AGwAYQByAEwAaQBiAHIAZQAgAEYAcgBhAG4AawBsAGkAbgAgAGkAcwAgAGEAIAB0AHIAYQBkAGUAbQBhAHIAawAgAG8AZgAgAEkAbQBwAGEAbABsAGEAcgBpACAAVAB5AHAAZQAuAEkAbQBwAGEAbABsAGEAcgBpACAAVAB5AHAAZQBQAGEAYgBsAG8AIABJAG0AcABhAGwAbABhAHIAaQAsACAAUgBvAGQAcgBpAGcAbwAgAEYAdQBlAG4AegBhAGwAaQBkAGEALAAgAE4AaAB1AG4AZwAgAE4AZwB1AHkAZQBuAEwAaQBiAHIAZQAgAEYAcgBhAG4AawBsAGkAbgAgAGkAcwAgAGEAIAByAGUAaQBuAHQAZQByAHAAcgBlAHQAYQB0AGkAbwBuACAAYQBuAGQAIABlAHgAcABhAG4AcwBpAG8AbgAgAG8AZgAgAHQAaABlACAAMQA5ADEAMgAgAE0AbwByAHIAaQBzACAARgB1AGwAbABlAHIAIABCAGUAbgB0AG8AbiAZAHMAIABjAGwAYQBzAHMAaQBjAC4AaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGkAbQBwAGEAbABsAGEAcgBpAC4AYwBvAG0ALwBUAGgAaQBzACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAGkAcwAgAGwAaQBjAGUAbgBzAGUAZAAgAHUAbgBkAGUAcgAgAHQAaABlACAAUwBJAEwAIABPAHAAZQBuACAARgBvAG4AdAAgAEwAaQBjAGUAbgBzAGUALAAgAFYAZQByAHMAaQBvAG4AIAAxAC4AMQAuACAAVABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABpAHMAIABhAHYAYQBpAGwAYQBiAGwAZQAgAHcAaQB0AGgAIABhACAARgBBAFEAIABhAHQAOgAgAGgAdAB0AHAAOgAvAC8AcwBjAHIAaQBwAHQAcwAuAHMAaQBsAC4AbwByAGcALwBPAEYATABoAHQAdABwADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8ATwBGAEwAVwBlAGkAZwBoAHQAVABoAGkAbgBFAHgAdAByAGEATABpAGcAaAB0AEwAaQBnAGgAdABNAGUAZABpAHUAbQBTAGUAbQBpAEIAbwBsAGQAQgBvAGwAZABFAHgAdAByAGEAQgBvAGwAZABCAGwAYQBjAGsAAAIAAAAAAAD/tQAyAAAAAAAAAAAAAAAAAAAAAAAAAAACWgAAAAIAAwAkAMkBAgEDAQQBBQEGAQcAxwEIAQkBCgELAQwBDQBiAQ4ArQEPARABEQESAGMBEwCuAJABFAAlACYA/QD/AGQBFQEWACcA6QEXARgBGQAoAGUBGgEbAMgBHAEdAR4BHwEgASEAygEiASMAywEkASUBJgEnASgAKQAqAPgBKQEqASsAKwEsAS0BLgAsAMwBLwDNATAAzgD6ATEAzwEyATMBNAE1ATYALQE3AC4BOAAvATkBOgE7ATwA4gAwADEBPQE+AT8BQAFBAGYAMgDQAUIA0QFDAUQBRQFGAUcBSABnAUkA0wFKAUsBTAFNAU4BTwFQAVEBUgFTAVQAkQFVAK8AsAAzAO0ANAA1AVYBVwFYAVkBWgFbADYBXADkAPsBXQFeAV8BYAA3AWEBYgFjAWQBZQA4ANQBZgDVAWcAaAFoANYBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQA5ADoBdgF3AXgBeQA7ADwA6wF6ALsBewF8AX0BfgA9AX8A5gGAAYEARABpAYIBgwGEAYUBhgGHAGsBiAGJAYoBiwGMAY0AbAGOAGoBjwGQAZEBkgBuAZMAbQCgAZQARQBGAP4BAABvAZUBlgBHAOoBlwEBAZgASABwAZkBmgByAZsBnAGdAZ4BnwGgAHMBoQGiAHEBowGkAaUBpgGnAagASQBKAPkBqQGqAasASwGsAa0BrgBMANcAdAGvAHYBsAB3AbEBsgB1AbMBtAG1AbYBtwBNAbgBuQBOAboBuwBPAbwBvQG+Ab8A4wBQAFEBwAHBAcIBwwHEAHgAUgB5AcUAewHGAccByAHJAcoBywB8AcwAegHNAc4BzwHQAdEB0gHTAdQB1QHWAdcAoQHYAH0AsQBTAO4AVABVAdkB2gHbAdwB3QHeAFYB3wDlAPwB4AHhAeIAiQBXAeMB5AHlAeYB5wBYAH4B6ACAAekAgQHqAH8B6wHsAe0B7gHvAfAB8QHyAfMB9AH1AfYB9wBZAFoB+AH5AfoB+wBbAFwA7AH8ALoB/QH+Af8CAABdAgEA5wICAgMAwADBAJ0AngCbABMAFAAVABYAFwAYABkAGgAbABwCBAIFAgYCBwIIAgkCCgILAgwCDQIOAg8CEAIRAhICEwIUAhUCFgIXAhgCGQIaAhsCHAIdAh4CHwIgAiECIgIjAiQCJQImAicCKAIpAioCKwC8APQCLAItAPUA9gIuAi8CMAIxAA0APwDDAIcAHQAPAKsABACjAAYAEQAiAKIABQAKAB4AEgBCAF4AYAA+AEAACwAMALMAsgAQAjIAqQCqAL4AvwDFALQAtQC2ALcAxAIzAIQCNAC9AAcCNQI2AKYA9wI3AjgCOQI6AIUCOwCWAA4A7wDwALgAIACPACEAHwCVAJQAkwCnAGEApAI8AJIAnAI9Aj4AmgCZAKUAmAI/AAgAxgC5ACMACQCIAIYAiwCKAkAAjACDAF8A6AJBAIIAwgJCAkMAQQJEAkUCRgJHAkgCSQJKAksCTAJNAk4CTwJQAlECUgJTAlQCVQJWAlcCWAJZAI0A2wDhAN4A2ACOANwAQwDfANoA4ADdANkCWgJbAlwCXQJeAl8CYAJhAmIGQWJyZXZlB3VuaTFFQUUHdW5pMUVCNgd1bmkxRUIwB3VuaTFFQjIHdW5pMUVCNAd1bmkxRUE0B3VuaTFFQUMHdW5pMUVBNgd1bmkxRUE4B3VuaTFFQUEHdW5pMDIwMAd1bmkxRUEwB3VuaTFFQTIHdW5pMDIwMgdBbWFjcm9uB0FvZ29uZWsKQXJpbmdhY3V0ZQdBRWFjdXRlC0NjaXJjdW1mbGV4CkNkb3RhY2NlbnQGRGNhcm9uBkRjcm9hdAd1bmkxRTBDBkVicmV2ZQZFY2Fyb24HdW5pMUVCRQd1bmkxRUM2B3VuaTFFQzAHdW5pMUVDMgd1bmkxRUM0B3VuaTAyMDQKRWRvdGFjY2VudAd1bmkxRUI4B3VuaTFFQkEHdW5pMDIwNgdFbWFjcm9uB0VvZ29uZWsHdW5pMUVCQwtHY2lyY3VtZmxleAxHY29tbWFhY2NlbnQKR2RvdGFjY2VudARIYmFyC0hjaXJjdW1mbGV4B3VuaTFFMjQGSWJyZXZlB3VuaTAyMDgHdW5pMUVDQQd1bmkxRUM4B3VuaTAyMEEHSW1hY3JvbgdJb2dvbmVrBkl0aWxkZQtKY2lyY3VtZmxleAxLY29tbWFhY2NlbnQGTGFjdXRlBkxjYXJvbgxMY29tbWFhY2NlbnQETGRvdAZOYWN1dGUGTmNhcm9uDE5jb21tYWFjY2VudAd1bmkxRTQ0A0VuZwZPYnJldmUHdW5pMUVEMAd1bmkxRUQ4B3VuaTFFRDIHdW5pMUVENAd1bmkxRUQ2B3VuaTAyMEMHdW5pMUVDQwd1bmkxRUNFBU9ob3JuB3VuaTFFREEHdW5pMUVFMgd1bmkxRURDB3VuaTFFREUHdW5pMUVFMA1PaHVuZ2FydW1sYXV0B3VuaTAyMEUHT21hY3Jvbgd1bmkwMUVBC09zbGFzaGFjdXRlBlJhY3V0ZQZSY2Fyb24MUmNvbW1hYWNjZW50B3VuaTAyMTAHdW5pMUU1QQd1bmkwMjEyBlNhY3V0ZQtTY2lyY3VtZmxleAxTY29tbWFhY2NlbnQHdW5pMUU2Mgd1bmkwMThGBFRiYXIGVGNhcm9uB3VuaTAxNjIHdW5pMDIxQQd1bmkxRTZDBlVicmV2ZQd1bmkwMjE0B3VuaTFFRTQHdW5pMUVFNgVVaG9ybgd1bmkxRUU4B3VuaTFFRjAHdW5pMUVFQQd1bmkxRUVDB3VuaTFFRUUNVWh1bmdhcnVtbGF1dAd1bmkwMjE2B1VtYWNyb24HVW9nb25lawVVcmluZwZVdGlsZGUGV2FjdXRlC1djaXJjdW1mbGV4CVdkaWVyZXNpcwZXZ3JhdmULWWNpcmN1bWZsZXgHdW5pMUVGNAZZZ3JhdmUHdW5pMUVGNgd1bmkxRUY4BlphY3V0ZQpaZG90YWNjZW50B3VuaTFFOTIGYWJyZXZlB3VuaTFFQUYHdW5pMUVCNwd1bmkxRUIxB3VuaTFFQjMHdW5pMUVCNQd1bmkxRUE1B3VuaTFFQUQHdW5pMUVBNwd1bmkxRUE5B3VuaTFFQUIHdW5pMDIwMQd1bmkxRUExB3VuaTFFQTMHdW5pMDIwMwdhbWFjcm9uB2FvZ29uZWsKYXJpbmdhY3V0ZQdhZWFjdXRlC2NjaXJjdW1mbGV4CmNkb3RhY2NlbnQGZGNhcm9uB3VuaTFFMEQGZWJyZXZlBmVjYXJvbgd1bmkxRUJGB3VuaTFFQzcHdW5pMUVDMQd1bmkxRUMzB3VuaTFFQzUHdW5pMDIwNQplZG90YWNjZW50B3VuaTFFQjkHdW5pMUVCQgd1bmkwMjA3B2VtYWNyb24HZW9nb25lawd1bmkxRUJEB3VuaTAyNTkLZ2NpcmN1bWZsZXgMZ2NvbW1hYWNjZW50Cmdkb3RhY2NlbnQEaGJhcgtoY2lyY3VtZmxleAd1bmkxRTI1BmlicmV2ZQd1bmkwMjA5CWkubG9jbFRSSwd1bmkxRUNCB3VuaTFFQzkHdW5pMDIwQgdpbWFjcm9uB2lvZ29uZWsGaXRpbGRlB3VuaTAyMzcLamNpcmN1bWZsZXgMa2NvbW1hYWNjZW50DGtncmVlbmxhbmRpYwZsYWN1dGUGbGNhcm9uDGxjb21tYWFjY2VudARsZG90Bm5hY3V0ZQZuY2Fyb24MbmNvbW1hYWNjZW50B3VuaTFFNDUDZW5nBm9icmV2ZQd1bmkxRUQxB3VuaTFFRDkHdW5pMUVEMwd1bmkxRUQ1B3VuaTFFRDcHdW5pMDIwRAd1bmkxRUNEB3VuaTFFQ0YFb2hvcm4HdW5pMUVEQgd1bmkxRUUzB3VuaTFFREQHdW5pMUVERgd1bmkxRUUxDW9odW5nYXJ1bWxhdXQHdW5pMDIwRgdvbWFjcm9uB3VuaTAxRUILb3NsYXNoYWN1dGUGcmFjdXRlBnJjYXJvbgxyY29tbWFhY2NlbnQHdW5pMDIxMQd1bmkxRTVCB3VuaTAyMTMGc2FjdXRlC3NjaXJjdW1mbGV4DHNjb21tYWFjY2VudAd1bmkxRTYzBHRiYXIGdGNhcm9uB3VuaTAxNjMHdW5pMDIxQgd1bmkxRTZEBnVicmV2ZQd1bmkwMjE1B3VuaTFFRTUHdW5pMUVFNwV1aG9ybgd1bmkxRUU5B3VuaTFFRjEHdW5pMUVFQgd1bmkxRUVEB3VuaTFFRUYNdWh1bmdhcnVtbGF1dAd1bmkwMjE3B3VtYWNyb24HdW9nb25lawV1cmluZwZ1dGlsZGUGd2FjdXRlC3djaXJjdW1mbGV4CXdkaWVyZXNpcwZ3Z3JhdmULeWNpcmN1bWZsZXgHdW5pMUVGNQZ5Z3JhdmUHdW5pMUVGNwd1bmkxRUY5BnphY3V0ZQp6ZG90YWNjZW50B3VuaTFFOTMJemVyby5zdWJzCG9uZS5zdWJzCHR3by5zdWJzCnRocmVlLnN1YnMJZm91ci5zdWJzCWZpdmUuc3VicwhzaXguc3VicwpzZXZlbi5zdWJzCmVpZ2h0LnN1YnMJbmluZS5zdWJzCXplcm8uZG5vbQhvbmUuZG5vbQh0d28uZG5vbQp0aHJlZS5kbm9tCWZvdXIuZG5vbQlmaXZlLmRub20Ic2l4LmRub20Kc2V2ZW4uZG5vbQplaWdodC5kbm9tCW5pbmUuZG5vbQl6ZXJvLm51bXIIb25lLm51bXIIdHdvLm51bXIKdGhyZWUubnVtcglmb3VyLm51bXIJZml2ZS5udW1yCHNpeC5udW1yCnNldmVuLm51bXIKZWlnaHQubnVtcgluaW5lLm51bXIHdW5pMjA3MAd1bmkwMEI5B3VuaTAwQjIHdW5pMDBCMwd1bmkyMDc0B3VuaTIwNzUHdW5pMjA3Ngd1bmkyMDc3B3VuaTIwNzgHdW5pMjA3OQd1bmkyMTUzB3VuaTIxNTQJb25lZWlnaHRoDHRocmVlZWlnaHRocwtmaXZlZWlnaHRocwxzZXZlbmVpZ2h0aHMHdW5pMDBBRAd1bmkwMEEwDWNvbG9ubW9uZXRhcnkEZG9uZwRFdXJvBGxpcmEHdW5pMjBCQQd1bmkyMEE2B3VuaTIwQjkHdW5pMjBBOQhlbXB0eXNldAd1bmkyMTI2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DqQLmA6kDF4OpAuSDqQMXg6kC5gOpA6kDqQLng6kDqQOpAueDqQOpA6kC54OpA6kDqQLng6kDqQOpAueDqQLtgu8C8IOpAu2C7wLwg6kC7YLvAukDqQLtgu8C8IOpAu2C7wLqg6kC7YLvAuqDqQLtgu8C6oOpAu2C7wLqg6kC7YLvAuqDqQLtgu8C6oOpAu2C7wLwg6kC7YLvAvCDqQLtgu8C8IOpAu2C7wLwg6kC7YLvAvCDqQLtgu8C8IOpAu2C7wLsA6kC7YLvAvCDqQLtgu8C8IOpAu2C7wLwg6kC9QOpAvaDqQL1A6kC8gOpAvUDqQLzg6kC9QOpAvaDqQL1A6kC9oOpAvyDqQL+A6kC+AOpAvmDqQL8g6kC+wOpAvyDqQL+A6kDBAMFgwcDqQMEAwWDBwOpAwQDBYL/g6kDBAMFgwEDqQMEAwWDBwOpAwQDBYMHA6kDBAMFgwcDqQMEAwWDBwOpAwQDBYMHA6kDBAMFgwcDqQMEAwWDAoOpAwQDBYMHA6kDBAMFgwcDqQMEAwWDBwOpA6kDqQN0g6kDqQOpAwiDqQMKA6kDC4OpAwoDqQMLg6kDNwOpAw0DDoM3A6kDDQMOgzcDqQMNAw6DNwOpAw0DDoM3A6kDDQMOgzcDqQMNAw6DEAOpAxGDqQMQA6kDEYOpAxADqQMRg6kDEAOpAxGDqQMQA6kDEYOpAxADqQMRg6kDF4MZAxqDHAMXgxkDGoMcAxeDGQMTAxwDF4MZAxSDHAMXgxkDFIMcAxeDGQMUgxwDF4MZAxSDHAMXgxkDFIMcAxeDGQMUgxwDF4MZAxqDHAMXgxkDGoMcAxeDGQMagxwDF4MZAxqDHAMXgxkDGoMcAxeDGQMagxwDF4MZAxqDHAMXgxkDGoMcAxeDGQMagxwDF4MZAxqDHAMXgxkDGoMcAxeDGQMagxwDF4MZAxYDHAMXgxkDGoMcAxeDGQMagxwDqQOpAy+DqQOpA6kDL4OpAxeDGQMagxwDHwOpAx2DqQMfA6kDHYOpAx8DqQMdg6kDHwOpAx2DqQMfA6kDHYOpAx8DqQMdg6kDHwOpAyCDqQMlA6kDI4OpAyUDqQMjg6kDJQOpAyODqQMlA6kDI4OpAyUDqQMiA6kDJQOpAyODqQMlA6kDI4OpAyUDqQMmg6kDJQOpAyaDqQMlA6kDJoOpAyUDqQMmg6kDJQOpAyaDqQMlA6kDJoOpAyyDLgMvgzEDLIMuAy+DMQMsgy4DKAMxAyyDLgMpgzEDLIMuAy+DMQMsgy4DL4MxAyyDLgMvgzEDLIMuAy+DMQMsgy4DL4MxAyyDLgMvgzEDLIMuAy+DMQMsgy4DL4MxAyyDLgMvgzEDLIMuAy+DMQMsgy4DL4MxAyyDLgMvgzEDLIMuAysDMQMsgy4DL4MxAyyDLgMvgzEDLIMuAy+DMQMsgy4DL4MxA6kDqQM0A6kDqQOpAzQDqQOpA6kDMoOpA6kDqQM0A6kDqQOpAzQDqQM3A6kDOIOpAzcDqQM4g6kDNwOpAzWDqQM3A6kDOIOpAzcDqQM4g6kDNwOpAziDqQM3A6kDOIOpAzcDqQM4g6kDOgOpAzuDqQM6A6kDO4OpAzoDqQM7g6kDOgOpAzuDqQM6A6kDO4OpA0GDQwNEg6kDQYNDA0SDqQNBg0MDPQOpA0GDQwM9A6kDQYNDAz0DqQNBg0MDPQOpA0GDQwM9A6kDQYNDAz0DqQNBg0MDPoOpA0GDQwM+g6kDQYNDAz6DqQNBg0MDPoOpA0GDQwM+g6kDQYNDAz6DqQNBg0MDRIOpA0GDQwNEg6kDQYNDA0SDqQNBg0MDRIOpA0GDQwNEg6kDQYNDA0ADqQNBg0MDRIOpA0GDQwNEg6kDQYNDA0SDqQNBg0MDRIOpA0GDQwNEg6kDqQOpA0YDqQOpA6kDRgOpA0kDqQNKg6kDSQOpA0qDqQNJA6kDSoOpA0kDqQNKg6kDSQOpA0eDqQNJA6kDSoOpA0wDqQOpA02DTAOpA6kDTYNMA6kDqQNNg0wDqQOpA02DU4NVA1aDqQNTg1UDVoOpA1ODVQNPA6kDU4NVA1aDqQNTg1UDUIOpA1ODVQNQg6kDU4NVA1CDqQNTg1UDUIOpA1ODVQNQg6kDU4NVA1CDqQNTg1UDVoOpA1ODVQNWg6kDU4NVA1aDqQNTg1UDVoOpA1ODVQNWg6kDU4NVA1aDqQNTg1UDUgOpA1ODVQNWg6kDU4NVA1aDqQNTg1UDVoOpA1gDWYNbA6kDqQOpA1+DqQOpA6kDXIOpA6kDqQNeA6kDqQOpA1+DqQOpA6kDX4OpA2KDqQNkA6kDYoOpA2QDqQNig6kDYQOpA2KDqQNkA6kDqQNog2oDqQOpA2iDagOpA6kDaINlg6kDqQNog2uDqQOpA2iDagOpA6kDaINqA6kDqQNog2oDqQOpA2iDagOpA6kDaINqA6kDqQNog2cDqQOpA2iDagOpA6kDaINqA6kDqQNog2oDqQOpA6kDagOpA6kDqQNrg6kDbQOpA6kDqQNtA6kDqQOpA4sDqQNug3ADiwOpA26DcAOLA6kDboNwA4sDqQNug3ADiwOpA26DcANxg6kDcwN0g3YDqQN3g6kDdgOpA3eDqQN2A6kDd4OpA3YDqQN3g6kDdgOpA3eDqQN2A6kDd4OpA4ODhQOGg4gDg4OFA4aDiAODg4UDeQOIA4ODhQN6g4gDg4OFA3qDiAODg4UDeoOIA4ODhQN6g4gDg4OFA3qDiAODg4UDeoOIA4ODhQOGg4gDg4OFA4aDiAODg4UDhoOIA4ODhQOGg4gDg4OFA4aDiAODg4UDhoOIA4ODhQOGg4gDg4OFA4aDiAODg4UDhoOIA4ODhQOGg4gDg4OFA4aDiAODg4UDhoOIA4ODhQN8A4gDg4OFA4aDiAODg4UDhoOIA32DfwOAg4IDfYN/A4CDggODg4UDhoOIA4sDqQOJg6kDiwOpA4mDqQOLA6kDiYOpA4sDqQOJg6kDiwOpA4mDqQOLA6kDiYOpA4sDqQOMg6kDj4OpA5EDqQOPg6kDkQOpA4+DqQORA6kDj4OpA5EDqQOPg6kDjgOpA4+DqQORA6kDj4OpA5EDqQOSg6kDqQOUA5KDqQOpA5QDkoOpA6kDlAOSg6kDqQOUA5KDqQOpA5QDkoOpA6kDlAOaA5uDnQOeg5oDm4OdA56DmgObg5WDnoOaA5uDlwOeg5oDm4OdA56DmgObg50DnoOaA5uDnQOeg5oDm4OdA56DmgObg50DnoOaA5uDnQOeg5oDm4OdA56DmgObg50DnoOaA5uDnQOeg5oDm4OdA56DmgObg50DnoOaA5uDnQOeg5oDm4OYg56DmgObg50DnoOaA5uDnQOeg5oDm4OdA56DmgObg50DnoOpA6kDoYOpA6kDqQOhg6kDqQOpA6ADqQOpA6kDoYOpA6kDqQOhg6kDpIOpA6YDqQOkg6kDpgOpA6SDqQOjA6kDpIOpA6YDqQOkg6kDpgOpA6SDqQOmA6kDpIOpA6YDqQOkg6kDpgOpA6kDqQOng6kDqQOpA6eDqQOpA6kDp4OpA6kDqQOng6kDqQOpA6eDqQAAQFeBHkAAQFcA5gAAQFdA2wAAQFbA4gAAQFeAAAAAQJ/AAsAAQFeAuYAAQKpAuYAAQF2A2wAAQF3AuYAAQFQAuYAAQFXA5gAAQFYA2wAAQFWA4gAAQFsAAAAAQIlAAoAAQFZAuYAAQF0A5gAAQF1A2wAAQF7AAAAAQF2AuYAAQFqAAAAAQFqAuYAAQFqA2wAAQFrAAAAAQFrAuYAAQCKA5gAAQCLA2wAAQCJA4gAAQCJAAAAAQCVAAoAAQCMAuYAAQEJA2wAAQFXAAAAAQF1AuYAAQCJAuYAAQGnAuYAAQFnAAAAAQGKAuYAAQF7A5gAAQF8A2wAAQF6A4gAAQF9AAAAAQHDAAkAAQF9AuYAAQIFAuYAAQFOAuYAAQFVAAAAAQFLA4gAAQE+A2wAAQE/AuYAAQFDAAAAAQFDAuYAAQFhA5gAAQFiA2wAAQFgA4gAAQFfAAAAAQGnAAkAAQFjAuYAAQJ4AuYAAQIAA2wAAQIBAuYAAQE5A2wAAQE/AAAAAQE6AuYAAQE4AAAAAQExAuYAAQEIAsQAAQEJApgAAQEHArQAAQENAAAAAQHDAA4AAQEKAhIAAQHFAhIAAQEmApgAAQEpAAAAAQEnAhIAAQEuAAAAAQJzAuYAAQEnAsQAAQEoApgAAQEmArQAAQEsAAAAAQGJABIAAQEpAhIAAQEYAAAAAQC4AgAAAQEVAhIAAQEZAsQAAQEaApgAAQEbAhIAAQEgA3YAAQEvAAAAAQFFAhIAAQBtAsQAAQBsArQAAQB7AA0AAQBvAhIAAQBuApgAAQERAAAAAQBvAuYAAQEAAuYAAQB5AAAAAQB5AuYAAQEKAuYAAQElAAAAAQEvAhIAAQErAsQAAQEsApgAAQEqArQAAQEoAAAAAQFpAAoAAQEgAhIAAQGrAhIAAQEqAAAAAQFrAAoAAQEtAhIAAQGtAhIAAQDLAhIAAQBvAAAAAQDIArQAAQDpApgAAQDyAAAAAQDqAhIAAQDnAAAAAQFQAvMAAQEKAsQAAQELApgAAQEJArQAAQEMAAAAAQHYAAsAAQEMAhIAAQIGAhIAAQGLApgAAQGMAhIAAQD+ApgAAQFJAAAAAQD/AhIAAQD0AhIAAQAAAAAABgEAAAEACAABAJQADAABALQAGgABAAUCNQI3Aj0CRQJIAAUADAASABgAHgAkAAH/cwKYAAH/cgLEAAH/cgK0AAEAiQLEAAEAiwKYAAYCAAABAAgAAQAMABQAAQAaACoAAQACAjQCPgABAAECPgACAAAACgAAABQAAf/xAuYAAQAEAAH/9AISAAYBAAABAAgAAQAMACIAAQAsAOwAAgADAi8CMwAAAjUCPQAFAlECWAAOAAIAAQJRAlgAAAAWAAAAWgAAAGAAAABmAAAAhAAAAGwAAAByAAAAcgAAAHIAAAB4AAAAfgAAAIQAAAC6AAAAigAAAJAAAACWAAAAlgAAAJwAAACiAAAAqAAAAK4AAAC0AAAAugAB/6YCEgAB/90CEgAB/3ECEgAB/x8CEgAB/3QCEgAB/5ICEgAB/3MCEgAB/3gCEgAB/4ACEgAB/3UCEgAB/sgCEgAB/s0CEgAB/toCEgAB/swCEgAB/tQCEgAB/t4CEgAB/uECEgAIABIAGAAeACQAKgAwADYAPAAB/sgDpQAB/sYCxAAB/ssCxAAB/tgCxAAB/ssCmAAB/tMCmAAB/t0CmAAB/uACmAABAAAACgFkBGoAAkRGTFQADmxhdG4ALAAEAAAAAP//AAoAAAAKABQAHgAoADoARABOAFgAYgA0AAhBWkUgAE5DQVQgAGpDUlQgAIZLQVogAKJNT0wgAL5ST00gANpUQVQgAPZUUksgARIAAP//AAoAAQALABUAHwApADsARQBPAFkAYwAA//8ACwACAAwAFgAgACoAMgA8AEYAUABaAGQAAP//AAsAAwANABcAIQArADMAPQBHAFEAWwBlAAD//wALAAQADgAYACIALAA0AD4ASABSAFwAZgAA//8ACwAFAA8AGQAjAC0ANQA/AEkAUwBdAGcAAP//AAsABgAQABoAJAAuADYAQABKAFQAXgBoAAD//wALAAcAEQAbACUALwA3AEEASwBVAF8AaQAA//8ACwAIABIAHAAmADAAOABCAEwAVgBgAGoAAP//AAsACQATAB0AJwAxADkAQwBNAFcAYQBrAGxhYWx0AophYWx0AophYWx0AophYWx0AophYWx0AophYWx0AophYWx0AophYWx0AophYWx0AophYWx0AopjY21wAppjY21wApJjY21wAppjY21wAppjY21wAppjY21wAppjY21wAppjY21wAppjY21wAppjY21wAppkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBkbm9tAqBmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZmcmFjAqZsaWdhArBsaWdhArBsaWdhArBsaWdhArBsaWdhArBsaWdhArBsaWdhArBsaWdhArBsaWdhArBsaWdhArBsb2NsArZsb2NsArxsb2NsAsJsb2NsAshsb2NsAs5sb2NsAtRsb2NsAtpsb2NsAuBudW1yAuZudW1yAuZudW1yAuZudW1yAuZudW1yAuZudW1yAuZudW1yAuZudW1yAuZudW1yAuZudW1yAuZvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxvcmRuAuxzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzaW5mAvRzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdWJzAvpzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwBzdXBzAwAAAAACAAAAAQAAAAIAAgADAAAAAQACAAAAAQAQAAAAAwARABIAEwAAAAEAFgAAAAEACwAAAAEABAAAAAEACgAAAAEABwAAAAEABgAAAAEABQAAAAEACAAAAAEACQAAAAEADwAAAAIAFAAVAAAAAQANAAAAAQAMAAAAAQAOABoANgCYATYBggHgAiQCJAJGAkYCRgJGAkYCWgJaAmgCmAJ2AoQCmAKmAu4DNgNYA4ADlgPEAAEAAAABAAgAAgAuABQBjQGOAJMAmgGNARsBjgFZAWABpAGlAaYBpwGoAakBqgGrAawBrQHCAAEAFAADAGgAkQCZAMUBGgEuAVcBXwGuAa8BsAGxAbIBswG0AbUBtgG3AdwAAwAAAAEACAABAIYACwAcACIALAA2AEAASgBUAF4AaAByAHwAAgEMARIABAG4AaQBrgGaAAQBpQGvAZsBuQAEAaYBsAGcAboABAGnAbEBnQG7AAQBqAGyAZ4BvAAEAakBswGfAb0ABAGqAbQBoAG+AAQBqwG1AaEBvwAEAawBtgGiAcAABAGtAbcBowHBAAIAAgELAQsAAAGQAZkAAQAGAAAAAgAKABwAAwAAAAECTgABADIAAQAAABcAAwAAAAECPAACABQAIAABAAAAFwABAAQCPgI/AkECQgACAAICLwIzAAACNQI9AAUABAAAAAEACAABAE4AAgAKACwABAAKABAAFgAcAlYAAgIxAlUAAgIyAlgAAgI5AlcAAgI7AAQACgAQABYAHAJSAAICMQJRAAICMgJUAAICOQJTAAICOwABAAICNQI3AAYAAAACAAoAJAADAAAAAgAUAC4AAQAUAAEAAAAYAAEAAQEgAAMAAAACABoAFAABABoAAQAAABgAAQABAc4AAQABAFoAAQAAAAEACAACAA4ABACTAJoBWQFgAAEABACRAJkBVwFfAAEAAAABAAgAAQAGAAcAAQABAQsAAQAAAAEACAABAMIACgABAAAAAQAIAAEAtAAoAAEAAAABAAgAAQCmABQAAQAAAAEACAABAAb/5gABAAEB3AABAAAAAQAIAAEAhAAeAAYAAAACAAoAIgADAAEAEgABADQAAAABAAAAGQABAAEBwgADAAEAEgABABwAAAABAAAAGQACAAEBpAGtAAAAAgABAa4BtwAAAAYAAAACAAoAJAADAAEALAABABIAAAABAAAAGQABAAIAAwDFAAMAAQASAAEAHAAAAAEAAAAZAAIAAQGQAZkAAAABAAIAaAEuAAQAAAABAAgAAQAUAAEACAABAAQCLAADAS4B1gABAAEAYQAEAAAAAQAIAAEAGgABAAgAAgAGAAwBiwACAQsBjAACASAAAQABAQEAAQAAAAEACAABAAYAAQABAAIBCwEaAAQAAAABAAgAAQAeAAIACgAUAAEABABeAAIBzgABAAQBJAACAc4AAQACAFoBIAABAAAAAQAIAAIAIgAOAY0BjgGNAY4BpAGlAaYBpwGoAakBqgGrAawBrQABAA4AAwBoAMUBLgGuAa8BsAGxAbIBswG0AbUBtgG3AAEAAAAQAAIAAQAUAAkACHdnaHQAKAAAACgAAADIAAAAAAEAAQEAAAAoAAABAgAAADIAAAEDAAAAQgAAAQQAAABUAAABBQAAAGoAAAEGAAAAggAAAQcAAACaAAABCAAAALIAAAEJAAAAyAAAAAEAAAABAAEAAATKAloAAAAABMwAAAAZABkAGQBNAFUAXQBdAGYAxQExAaMBqwIMAhUCdgLrA18DZwNv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);
+}
+@font-face {
+ font-family: gingham;
+ /*
+ * Gingham.ttf encoded in base64.
+ * This font is Copyright © 2016 by Christoph Koeberlin.
+ * http://christoph.koe.berlin
+ * Licensed as CC BY-NC-ND 4.0, see https://createcommons.org/licenses/by-nc-nd/4.0/
+ */
+ src: url(data:font/ttf;base64,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);
+}
+#test1 {
+ font-family: Arial, sans-serif;
+}
+#test2 {
+ font-family: franklin, sans-serif;
+}
+#test3 {
+ font-family: gingham, sans-serif;
+}
+#test4 {
+ font-family: Skia, sans-serif;
+}
diff --git a/layout/inspector/tests/chrome/test_fontVariationsAPI.xhtml b/layout/inspector/tests/chrome/test_fontVariationsAPI.xhtml
new file mode 100644
index 0000000000..959d3ee45e
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontVariationsAPI.xhtml
@@ -0,0 +1,196 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_fontVariationsAPI.css"?>
+<window title="Test for font variation axis API"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+// Which platform are we on?
+const kIsLinux = navigator.platform.indexOf("Linux") == 0;
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+
+// If it's an older macOS version (pre-Sierra), we don't expect the
+// @font-face examples to work, so just skip them.
+const kIsOldMac = navigator.oscpu.substr(navigator.oscpu.lastIndexOf(".") + 1) < 12;
+
+// We allow for some "fuzz" on axis values, given the conversions between
+// fixed-point and floating-point representations.
+const kEpsilon = 0.001;
+
+
+const LibreFranklinAxes = [
+ { tag: "wght", name: "Weight", minValue: 40, maxValue: 200, defaultValue: 40 },
+];
+
+const GinghamAxes = [
+ { tag: "wght", name: "Weight", minValue: 300, maxValue: 700, defaultValue: 300 },
+ { tag: "wdth", name: "Width", minValue: 1, maxValue: 150, defaultValue: 1 },
+];
+
+const SkiaAxes = [
+ { tag: "wght", name: "Weight", minValue: 0.48, maxValue: 3.2, defaultValue: 1.0 },
+ { tag: "wdth", name: "Width", minValue: 0.62, maxValue: 1.3, defaultValue: 1.0 },
+];
+
+function checkAxes(axes, reference) {
+ is(axes.length, reference.length, "number of variation axes");
+ for (var i = 0; i < Math.min(axes.length, reference.length); ++i) {
+ var v = axes[i];
+ var ref = reference[i];
+ is(v.tag, ref.tag, "axis tag");
+ is(v.name, ref.name, "axis name");
+ isfuzzy(v.minValue, ref.minValue, kEpsilon, "minimum value");
+ isfuzzy(v.maxValue, ref.maxValue, kEpsilon, "maximum value");
+ is(v.defaultValue, ref.defaultValue, "default value");
+ }
+}
+
+// Expected variation instances for each of our test fonts, sorted by name
+const LibreFranklinInstances = [
+ { name: "Black", values: [ { axis: "wght", value: 200 } ] },
+ { name: "Bold", values: [ { axis: "wght", value: 154 } ] },
+ { name: "ExtraBold", values: [ { axis: "wght", value: 178 } ] },
+ { name: "ExtraLight", values: [ { axis: "wght", value: 50 } ] },
+ { name: "Light", values: [ { axis: "wght", value: 66 } ] },
+ { name: "Medium", values: [ { axis: "wght", value: 106 } ] },
+ { name: "Regular", values: [ { axis: "wght", value: 84 } ] },
+ { name: "SemiBold", values: [ { axis: "wght", value: 130 } ] },
+ { name: "Thin", values: [ { axis: "wght", value: 40 } ] },
+];
+
+const GinghamInstances = [
+ { name: "Bold", values: [ { axis: "wght", value: 700 }, { axis: "wdth", value: 100 } ] },
+ { name: "Condensed Bold", values: [ { axis: "wght", value: 700 }, { axis: "wdth", value: 1 } ] },
+ { name: "Condensed Light", values: [ { axis: "wght", value: 300 }, { axis: "wdth", value: 1 } ] },
+ { name: "Condensed Regular", values: [ { axis: "wght", value: 400 }, { axis: "wdth", value: 1 } ] },
+ { name: "Light", values: [ { axis: "wght", value: 300 }, { axis: "wdth", value: 100 } ] },
+ { name: "Regular", values: [ { axis: "wght", value: 400 }, { axis: "wdth", value: 100 } ] },
+ { name: "Wide Bold", values: [ { axis: "wght", value: 700 }, { axis: "wdth", value: 150 } ] },
+ { name: "Wide Light", values: [ { axis: "wght", value: 300 }, { axis: "wdth", value: 150 } ] },
+ { name: "Wide Regular", values: [ { axis: "wght", value: 400 }, { axis: "wdth", value: 150 } ] },
+];
+
+const SkiaInstances = [
+ { name: "Black", values: [ { axis: "wght", value: 3.2 }, { axis: "wdth", value: 1 } ] },
+ { name: "Black Condensed", values: [ { axis: "wght", value: 3 }, { axis: "wdth", value: 0.7 } ] },
+ { name: "Black Extended", values: [ { axis: "wght", value: 3.2 }, { axis: "wdth", value: 1.3 } ] },
+ { name: "Bold", values: [ { axis: "wght", value: 1.95 }, { axis: "wdth", value: 1 } ] },
+ { name: "Condensed", values: [ { axis: "wght", value: 1 }, { axis: "wdth", value: 0.62 } ] },
+ { name: "Extended", values: [ { axis: "wght", value: 1 }, { axis: "wdth", value: 1.3 } ] },
+ { name: "Light", values: [ { axis: "wght", value: 0.48 }, { axis: "wdth", value: 1 } ] },
+ { name: "Light Condensed", values: [ { axis: "wght", value: 0.48 }, { axis: "wdth", value: 0.7 } ] },
+ { name: "Light Extended", values: [ { axis: "wght", value: 0.48 }, { axis: "wdth", value: 1.3 } ] },
+ { name: "Regular", values: [ { axis: "wght", value: 1 }, { axis: "wdth", value: 1 } ] },
+];
+
+function checkInstances(instances, reference) {
+ is(instances.length, reference.length, "number of variation instances");
+ instances.sort(function (a, b) {
+ return a.name.localeCompare(b.name, "en-US");
+ });
+ for (var i = 0; i < Math.min(instances.length, reference.length); ++i) {
+ var ref = reference[i];
+ var inst = instances[i];
+ is(inst.name, ref.name, "instance name");
+ is(inst.values.length, ref.values.length, "number of values");
+ for (var j = 0; j < Math.min(inst.values.length, ref.values.length); ++j) {
+ var v = inst.values[j];
+ is(v.axis, ref.values[j].axis, "axis");
+ isfuzzy(v.value, ref.values[j].value, kEpsilon, "value");
+ }
+ }
+}
+
+function RunTest() {
+ // Ensure we're running with font-variations enabled
+ SpecialPowers.pushPrefEnv(
+ {'set': [['layout.css.font-variations.enabled', true],
+ ['gfx.downloadable_fonts.keep_variation_tables', true],
+ ['gfx.downloadable_fonts.otl_validation', false]]},
+ function() {
+ var rng = document.createRange();
+ var elem, fonts, f;
+
+ // First test element uses Arial (or a substitute), which has no variations
+ elem = document.getElementById("test1");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts");
+ f = fonts[0];
+ is(f.getVariationAxes().length, 0, "no variations");
+ is(f.getVariationInstances().length, 0, "no instances");
+
+ if (!(kIsMac && kIsOldMac)) {
+ // Libre Franklin font should have a single axis: Weight.
+ elem = document.getElementById("test2");
+ elem.style.display = "block";
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts");
+ f = fonts[0];
+ is(f.name, "Libre Franklin", "font name");
+ checkAxes(f.getVariationAxes(), LibreFranklinAxes);
+ checkInstances(f.getVariationInstances(), LibreFranklinInstances);
+
+ // Gingham font should have two axes: Weight and Width.
+ elem = document.getElementById("test3");
+ elem.style.display = "block";
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts");
+ f = fonts[0];
+ is(f.name, "Gingham Regular", "font name");
+ checkAxes(f.getVariationAxes(), GinghamAxes);
+ checkInstances(f.getVariationInstances(), GinghamInstances);
+ }
+
+ if (kIsMac) {
+ // Only applies on macOS, where the Skia font is present
+ // by default, and has Weight and Width axes.
+ elem = document.getElementById("test4");
+ rng.selectNode(elem);
+ fonts = InspectorUtils.getUsedFontFaces(rng);
+ is(fonts.length, 1, "number of fonts");
+ f = fonts[0];
+ is(f.name, "Skia", "font name");
+ checkAxes(f.getVariationAxes(), SkiaAxes);
+ checkInstances(f.getVariationInstances(), SkiaInstances);
+ }
+
+ SimpleTest.finish();
+ }
+ );
+}
+
+ ]]>
+ </script>
+
+ <!-- html:body contains elements the test will inspect -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1323743"
+ target="_blank">Mozilla Bug 1323743</a>
+
+ <!-- Using a non-variation font -->
+ <div id="test1">Hello world</div>
+
+ <!-- This element uses a variation font loaded with @font-face.
+ display:none used here to ensure loading of the webfont is deferred until
+ after we have set prefs required to preserve the variation tables. -->
+ <div id="test2" style="display:none">Hello world</div>
+
+ <!-- This element also uses a variation font loaded with @font-face. -->
+ <div id="test3" style="display:none">Hello world</div>
+
+ <!-- For macOS only, we also test with the system-installed Skia font -->
+ <div id="test4">Hello world</div>
+ </body>
+
+</window>
diff --git a/layout/inspector/tests/chrome/test_getBlockLineCounts.html b/layout/inspector/tests/chrome/test_getBlockLineCounts.html
new file mode 100644
index 0000000000..07a9ca2c72
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_getBlockLineCounts.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<style>
+.test {
+ margin: 1em;
+ font: 13px monospace;
+ width: 16ch;
+}
+.cols {
+ columns: 3;
+}
+</style>
+</head>
+
+<body>
+ <div class="test"><span id="test1">one two three four five six seven</span></div>
+ <div class="test"><div id="test2">one two three four five six seven</div></div>
+ <div class="test cols"><div id="test3">one two three four five six seven</div></div>
+ <div class="test"><div id="test4" class="cols">one two three four five six seven</div></div>
+
+ <script>
+ const TEST_DATA = [
+ {
+ description: "Test line counts of a non-block element returns null",
+ testElem: "test1",
+ lineCounts: null,
+ },
+ {
+ description: "Test line count of a non-fragmented block",
+ testElem: "test2",
+ lineCounts: [3],
+ },
+ {
+ description: "Test line counts of a block that is fragmented across columns",
+ testElem: "test3",
+ lineCounts: [3, 3, 1],
+ },
+ {
+ description: "Test line counts when columns are specified directly on the block",
+ testElem: "test4",
+ lineCounts: [3, 3, 1],
+ },
+ ];
+
+ function check(a, b) {
+ if (a === null) {
+ return b === null;
+ }
+ if (a.length !== b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function run_tests() {
+ for (const { description, testElem, lineCounts } of TEST_DATA) {
+ info(description);
+
+ let target = document.getElementById(testElem);
+ let counts = InspectorUtils.getBlockLineCounts(target);
+
+ ok(check(counts, lineCounts), "got expected line counts");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ window.requestAnimationFrame(run_tests);
+ </script>
+</body>
diff --git a/layout/inspector/tests/chrome/test_getSelectorWarnings.html b/layout/inspector/tests/chrome/test_getSelectorWarnings.html
new file mode 100644
index 0000000000..7e42ee841e
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_getSelectorWarnings.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<style id="test"></style>
+<script>
+const sheet = test.sheet;
+
+function unconstrainedHas(index) {
+ return { index: index, kind: "UnconstrainedHas" };
+}
+
+function testRule(rule, expected) {
+ const index = sheet.insertRule(rule);
+ const warnings = sheet.rules[index].getSelectorWarnings();
+ is(warnings.length, expected.length, "Got expected number of warnings");
+ for (let i = 0; i < expected.length; i++) {
+ is(warnings[i].index, expected[i].index, "Warning is generated for expected index");
+ is(warnings[i].kind, expected[i].kind, "Warning kind is as expected");
+ }
+ sheet.deleteRule(index);
+}
+
+// No selector to check.
+testRule(".foo {}", []);
+// Fully constrained `:has`
+testRule(".bar:has(.foo), :is(#bar):has(.foo) {}", []);
+// Unconstrained `:has`: Selector around it is unconstrained
+testRule(":has(.foo) {}", [unconstrainedHas(0)]);
+testRule(":has(.foo) .bar {}", [unconstrainedHas(0)]);
+testRule(".bar:has(.foo), :has(.foo) {}", [unconstrainedHas(1)]);
+testRule("*:has(.foo), *|*:has(.foo) {}", [unconstrainedHas(0), unconstrainedHas(1)]);
+testRule(":is(.bar *):has(.foo), :is(.bar):has(.foo), :is(:where(.bar ~ *)):has(.foo) {}", [unconstrainedHas(0), unconstrainedHas(2)]);
+testRule(":is(.bar *):has(.foo), :is(.bar):has(.foo), :is(:where(.bar ~ *)):has(.foo) {}", [unconstrainedHas(0), unconstrainedHas(2)]);
+testRule(":is(.bar, *):has(.foo) {}", [unconstrainedHas(0)]);
+testRule(":is(:has(bar) *):has(.foo) {}", [unconstrainedHas(0)]);
+// Unconstrained `:has`: Selector inside it is unconstrained
+testRule(".bar:has(*) {}", [unconstrainedHas(0)]);
+testRule(".bar:has(* .foo), .bar:has(:is(*) .foo) {}", [unconstrainedHas(0), unconstrainedHas(1)]);
+testRule(".bar:has(:is(* .bar) .foo), .bar:has(:is(:where(* .baz) .bar) .foo) {}", [unconstrainedHas(0), unconstrainedHas(1)]);
+testRule(".bar:has(:is(*, .bar) .foo) {}", [unconstrainedHas(0)]);
+testRule(":is(:has(*) .bar):has(.foo) {}", [unconstrainedHas(0)]);
+testRule(":is(:has(*)):has(.foo) {}", [unconstrainedHas(0)]);
+</script>
diff --git a/layout/inspector/tests/chrome/test_ua_rule_modification.html b/layout/inspector/tests/chrome/test_ua_rule_modification.html
new file mode 100644
index 0000000000..0628e124a1
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_ua_rule_modification.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Test for bug 1539159</title>
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<p></p>
+<script>
+function start() {
+ let rules = InspectorUtils.getCSSStyleRules(document.querySelector("p"));
+ ok(rules.length > 0, "Should have found some rules");
+ is(rules[0].type, CSSRule.STYLE_RULE, "Should have found a style rule");
+
+ let selector = rules[0].selectorText;
+ isnot(selector, ".xxxxx", "Rule selector should not be something strange");
+
+ try {
+ rules[0].selectorText = "img";
+ } catch (ex) {
+ }
+ is(rules[0].selectorText, selector, "Selector text should be unchanged");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+document.addEventListener('DOMContentLoaded', start)
+</script>
diff --git a/layout/inspector/tests/chrome/test_ua_sheet_disable.html b/layout/inspector/tests/chrome/test_ua_sheet_disable.html
new file mode 100644
index 0000000000..65131f0fe7
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_ua_sheet_disable.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Test for bug 1534399</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<script>
+function start() {
+ let sheet = InspectorUtils.getAllStyleSheets(document, false)[0];
+ ok(sheet.href.indexOf("test.css") == -1, "Shouldn't have found the author sheet");
+
+ is(sheet.disabled, false, "Sheet should initially be enabled");
+
+ sheet.disabled = true;
+ is(sheet.disabled, false, "Shouldn't be able to disable a UA sheet");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+document.addEventListener('DOMContentLoaded', start)
+</script>
diff --git a/layout/inspector/tests/chrome/test_visited_style.html b/layout/inspector/tests/chrome/test_visited_style.html
new file mode 100644
index 0000000000..1b1ce4f3b6
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_visited_style.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<style>
+#target:visited, div {
+ color: lime;
+}
+
+#target:link, div {
+ color: pink;
+}
+</style>
+</head>
+<body>
+ <a href="test" id="target">test-target</a>
+
+ <script>
+ const VISITED_SELECTOR = "#target:visited";
+ const LINK_SELECTOR = "#target:link";
+
+ const TEST_DATA = [
+ {
+ description: "Test for visited style",
+ isVisitedTest: true,
+ validSelector: VISITED_SELECTOR,
+ invalidSelector: LINK_SELECTOR,
+ },
+ {
+ description: "Test for unvisited style",
+ isVisitedTest: false,
+ validSelector: LINK_SELECTOR,
+ invalidSelector: VISITED_SELECTOR,
+ },
+ ];
+
+ function start() {
+ const target = document.getElementById("target");
+
+ for (const { description, isVisitedTest,
+ validSelector, invalidSelector } of TEST_DATA) {
+ info(description);
+
+ const rules =
+ InspectorUtils.getCSSStyleRules(target, undefined, isVisitedTest);
+ ok(getRule(rules, validSelector),
+ `Rule of ${validSelector} is in rules`);
+ ok(!getRule(rules, invalidSelector),
+ `Rule of ${invalidSelector} is not in rules`);
+
+ const targetRule = getRule(rules, validSelector);
+ const isTargetSelectorMatched = targetRule.selectorMatchesElement(0, target, undefined, isVisitedTest);
+ const isDivSelectorMatched = targetRule.selectorMatchesElement(1, target, undefined, isVisitedTest);
+ ok(isTargetSelectorMatched,
+ `${validSelector} selector is matched for the rule`);
+ ok(!isDivSelectorMatched,
+ "div selector is not matched for the rule");
+ }
+
+ SimpleTest.finish();
+ }
+
+ function getRule(rules, selector) {
+ for (const rule of rules) {
+ if (rule.selectorText.startsWith(selector)) {
+ return rule;
+ }
+ }
+ return null;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ document.addEventListener('DOMContentLoaded', start);
+ </script>
+</body>
+</html>
diff --git a/layout/inspector/tests/file_bug522601.html b/layout/inspector/tests/file_bug522601.html
new file mode 100644
index 0000000000..59459f3110
--- /dev/null
+++ b/layout/inspector/tests/file_bug522601.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=522601
+-->
+<head>
+ <title>Test for Bug 522601</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="some content">
+ <h1>Blah</h1>
+</div>
+</body>
+</html>
diff --git a/layout/inspector/tests/file_getCSSStyleRules-alternate.html b/layout/inspector/tests/file_getCSSStyleRules-alternate.html
new file mode 100644
index 0000000000..742a8f2bd4
--- /dev/null
+++ b/layout/inspector/tests/file_getCSSStyleRules-alternate.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<link rel="alternate stylesheet" title="x" href="getCSSStyleRules-1.css">
+<unknowntagname></unknowntagname>
diff --git a/layout/inspector/tests/file_getCSSStyleRules-default.html b/layout/inspector/tests/file_getCSSStyleRules-default.html
new file mode 100644
index 0000000000..f810799199
--- /dev/null
+++ b/layout/inspector/tests/file_getCSSStyleRules-default.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<link rel="stylesheet" href="getCSSStyleRules-1.css">
+<unknowntagname></unknowntagname>
diff --git a/layout/inspector/tests/getCSSStyleRules-1.css b/layout/inspector/tests/getCSSStyleRules-1.css
new file mode 100644
index 0000000000..db989304d7
--- /dev/null
+++ b/layout/inspector/tests/getCSSStyleRules-1.css
@@ -0,0 +1,3 @@
+unknowntagname {
+ z-index: 1;
+}
diff --git a/layout/inspector/tests/getCSSStyleRules-2.css b/layout/inspector/tests/getCSSStyleRules-2.css
new file mode 100644
index 0000000000..026cf15266
--- /dev/null
+++ b/layout/inspector/tests/getCSSStyleRules-2.css
@@ -0,0 +1,3 @@
+unknowntagname {
+ z-index: 2;
+}
diff --git a/layout/inspector/tests/mochitest.toml b/layout/inspector/tests/mochitest.toml
new file mode 100644
index 0000000000..3db9693c20
--- /dev/null
+++ b/layout/inspector/tests/mochitest.toml
@@ -0,0 +1,82 @@
+[DEFAULT]
+prefs = [
+ "layout.css.basic-shape-rect.enabled=true",
+ "layout.css.basic-shape-xywh.enabled=true",
+ "layout.css.properties-and-values.enabled=true",
+ "dom.customHighlightAPI.enabled=true",
+]
+support-files = [
+ "bug1202095.css",
+ "bug1202095-2.css",
+ "bug856317.css",
+ "file_bug522601.html",
+]
+
+["test_bug462787.html"]
+
+["test_bug462789.html"]
+
+["test_bug522601-shadow.xhtml"]
+
+["test_bug536379-2.html"]
+
+["test_bug536379.html"]
+
+["test_bug557726.html"]
+
+["test_bug609549-shadow.xhtml"]
+
+["test_bug856317.html"]
+
+["test_bug877690.html"]
+
+["test_bug1006595.html"]
+
+["test_color_to_rgba.html"]
+
+["test_containing_block_of.html"]
+
+["test_css_property_is_shorthand.html"]
+
+["test_getCSSPseudoElementNames.html"]
+
+["test_getCSSStyleRules.html"]
+support-files = [
+ "file_getCSSStyleRules-default.html",
+ "file_getCSSStyleRules-alternate.html",
+ "getCSSStyleRules-1.css",
+ "getCSSStyleRules-2.css",
+]
+
+["test_getCSSStyleRules_pseudo.html"]
+
+["test_getCSSStyleRules_slotted.html"]
+
+["test_getRegisteredCssHighlights.html"]
+
+["test_getRegisteredCustomProperties.html"]
+
+["test_getRelativeRuleLine.html"]
+
+["test_getRuleIndex.html"]
+
+["test_get_all_style_sheets.html"]
+
+["test_is_element_themed.html"]
+skip-if = ["os == 'android'"]
+
+["test_is_valid_css_color.html"]
+
+["test_isinheritableproperty.html"]
+
+["test_parseStyleSheet.html"]
+
+["test_parseStyleSheetImport.html"]
+
+["test_parseStyleSheet_nested.html"]
+
+["test_rgba_to_color_name.html"]
+
+["test_selectormatcheselement.html"]
+
+["test_supports.html"]
diff --git a/layout/inspector/tests/test_bug1006595.html b/layout/inspector/tests/test_bug1006595.html
new file mode 100644
index 0000000000..fefb92c3ae
--- /dev/null
+++ b/layout/inspector/tests/test_bug1006595.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1006595
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1006595</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1006595 **/
+
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ function arraysEqual(arr1, arr2, message) {
+ is(arr1.length, arr2.length, message + " length");
+ for (var i = 0; i < arr1.length; ++i) {
+ is(arr1[i], arr2[i], message + " element at index " + i);
+ }
+ }
+
+ var paddingSubProps = InspectorUtils.getSubpropertiesForCSSProperty("padding");
+ arraysEqual(paddingSubProps,
+ [ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left" ],
+ "'padding' subproperties");
+
+ var displaySubProps = InspectorUtils.getSubpropertiesForCSSProperty("color");
+ arraysEqual(displaySubProps, [ "color" ],
+ "'color' subproperties");
+
+ var varProps = InspectorUtils.getSubpropertiesForCSSProperty("--foo");
+ arraysEqual(varProps, ["--foo"], "'--foo' subproperties");
+
+ try {
+ InspectorUtils.cssPropertySupportsType("padding", 0);
+ ok(false, "Invalid types throw");
+ } catch (ex) {
+ ok(true, "Invalid types don't crash");
+ }
+
+ ok(InspectorUtils.cssPropertyIsShorthand("padding"), "'padding' is a shorthand")
+ ok(!InspectorUtils.cssPropertyIsShorthand("color"), "'color' is not a shorthand")
+
+ ok(!InspectorUtils.cssPropertySupportsType("padding", "color"),
+ "'padding' can't be a color");
+
+ ok(InspectorUtils.cssPropertySupportsType("color", "color"),
+ "'color' can be a color");
+ ok(InspectorUtils.cssPropertySupportsType("background", "color"),
+ "'background' can be a color");
+ ok(!InspectorUtils.cssPropertySupportsType("background-image", "color"),
+ "'background-image' can't be a color");
+
+ ok(InspectorUtils.cssPropertySupportsType("background-image", "gradient"),
+ "'background-image' can be a gradient");
+ ok(InspectorUtils.cssPropertySupportsType("background", "gradient"),
+ "'background' can be a gradient");
+ ok(!InspectorUtils.cssPropertySupportsType("background-color", "gradient"),
+ "'background-color' can't be a gradient");
+
+ ok(InspectorUtils.cssPropertySupportsType("transition", "timing-function"),
+ "'transition' can be a timing function");
+ ok(InspectorUtils.cssPropertySupportsType("transition-timing-function", "timing-function"),
+ "'transition-duration' can be a timing function");
+ ok(!InspectorUtils.cssPropertySupportsType("background-color", "timing-function"),
+ "'background-color' can't be a timing function");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1006595">Mozilla Bug 1006595</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug462787.html b/layout/inspector/tests/test_bug462787.html
new file mode 100644
index 0000000000..62548bd02c
--- /dev/null
+++ b/layout/inspector/tests/test_bug462787.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462787
+-->
+<head>
+ <title>Test for Bug 462787</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462787">Mozilla Bug 462787</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 462787 **/
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function do_test() {
+ try {
+ InspectorUtils.getCSSStyleRules(null);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ is(e.name, "TypeError", "got the expected exception");
+ }
+
+ try {
+ InspectorUtils.getRuleLine(null);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ is(e.name, "TypeError", "got the expected exception");
+ }
+
+ try {
+ InspectorUtils.isIgnorableWhitespace(null);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ is(e.name, "TypeError", "got the expected exception");
+ }
+
+ try {
+ InspectorUtils.getParentForNode(null, true);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ is(e.name, "TypeError", "got the expected exception");
+ }
+
+ try {
+ InspectorUtils.getContentState(null);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ is(e.name, "TypeError", "got the expected exception");
+ }
+
+ try {
+ InspectorUtils.setContentState(null, false);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ is(e.name, "TypeError", "got the expected exception");
+ }
+
+ try {
+ InspectorUtils.setContentState(document.documentElement, 3);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ ok(true, "Threw");
+ }
+
+ try {
+ InspectorUtils.removeContentState(document.documentElement, 3);
+ ok(false, "expected an exception");
+ }
+ catch(e) {
+ ok(true, "Threw");
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug462789.html b/layout/inspector/tests/test_bug462789.html
new file mode 100644
index 0000000000..e213fa831e
--- /dev/null
+++ b/layout/inspector/tests/test_bug462789.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462789
+-->
+<head>
+ <title>Test for Bug 462789</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462789">Mozilla Bug 462789</a>
+<p id="display"><iframe id="bug462789_iframe" srcdoc="<html><head><style>*{color:black;}</style></head><body>xxx" style="display: none;"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 462789 **/
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function do_test() {
+ const DOCUMENT_NODE_TYPE = 9;
+
+ var iframe = document.getElementById("bug462789_iframe");
+ var docElement = iframe.contentDocument.documentElement;
+ var body = docElement.children[1];
+ var rule = iframe.contentDocument.styleSheets[0].cssRules[0];
+ var text = body.firstChild;
+
+ try {
+ var res = InspectorUtils.getCSSStyleRules(docElement);
+ is(res.length, 0, "getCSSStyleRules");
+ res = InspectorUtils.getCSSStyleRules(body);
+ is(res.length, 0, "getCSSStyleRules");
+ }
+ catch(e) { ok(false, "got an unexpected exception:" + e); }
+
+ try {
+ var res = InspectorUtils.getRuleLine(rule);
+ is(res, 1, "getRuleLine");
+ }
+ catch(e) { ok(false, "got an unexpected exception:" + e); }
+
+ try {
+ var res = InspectorUtils.isIgnorableWhitespace(text);
+ is(res, false, "isIgnorableWhitespace");
+ }
+ catch(e) { ok(false, "got an unexpected exception:" + e); }
+
+ try {
+ var res = InspectorUtils.getParentForNode(docElement, true);
+ is(res.nodeType, DOCUMENT_NODE_TYPE, "getParentForNode(docElement, true)");
+ res = InspectorUtils.getParentForNode(text, true);
+ is(res.tagName, "BODY", "getParentForNode(text, true)");
+ }
+ catch(e) { ok(false, "got an unexpected exception:" + e); }
+
+ try {
+ InspectorUtils.getContentState(docElement);
+ ok(true, "Should not throw");
+ }
+ catch(e) { ok(false, "Got an exception: " + e); }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug522601-shadow.xhtml b/layout/inspector/tests/test_bug522601-shadow.xhtml
new file mode 100644
index 0000000000..d790527791
--- /dev/null
+++ b/layout/inspector/tests/test_bug522601-shadow.xhtml
@@ -0,0 +1,273 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=522601
+-->
+<head>
+ <title>Test for Bug 522601</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<template id="template"><div><slot/></div><slot name="foo"/></template>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=522601">Mozilla Bug 522601</a>
+<custom-element id="display">
+ <span slot="foo" id="s">This is some text</span>
+ More text
+ <b id="b">Even more <i id="i1">Italic</i>text<i id="i2">And more italic</i></b></custom-element>
+<div id="content" style="display: none">
+</div>
+<div id="subdoc">
+ <iframe id="frame1" src="file_bug522601.html">frame text</iframe>
+</div>
+<pre id="test">
+<script>
+<![CDATA[
+
+/** Test for Bug 522601 **/
+SimpleTest.waitForExplicitFinish();
+
+customElements.define("custom-element", class extends HTMLElement {
+ constructor() {
+ super();
+ const template = document.getElementById("template");
+ const shadowRoot = this.attachShadow({mode: "open"})
+ .appendChild(template.content.cloneNode(true));
+ }
+});
+
+function testFunc(walker, func, expectedNode, str) {
+ var oldCurrent = SpecialPowers.unwrap(walker.currentNode);
+ var newNode = SpecialPowers.unwrap(walker[func]());
+ is(newNode, expectedNode, "Unexpected node after " + str);
+ is(SpecialPowers.unwrap(walker.currentNode), newNode ? newNode : oldCurrent,
+ "Unexpected current node after " + str);
+}
+
+addLoadEvent(function() {
+ var walkerSubDocument =
+ SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(SpecialPowers.Ci.inIDeepTreeWalker);
+ walkerSubDocument.showAnonymousContent = false;
+ walkerSubDocument.showSubDocuments = true;
+ walkerSubDocument.init($("frame1"));
+
+ is(SpecialPowers.unwrap(walkerSubDocument.currentNode), $("frame1"), "Unexpected sub-doc root");
+ testFunc(walkerSubDocument, "firstChild", $("frame1").contentDocument.doctype,
+ "step to sub documents doctype");
+ testFunc(walkerSubDocument, "nextSibling", $("frame1").contentDocument.documentElement,
+ "step to sub documents documentElement");
+
+ walkerSubDocument =
+ SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(SpecialPowers.Ci.inIDeepTreeWalker);
+ walkerSubDocument.showAnonymousContent = false;
+ walkerSubDocument.showSubDocuments = true;
+ walkerSubDocument.showDocumentsAsNodes = true;
+ walkerSubDocument.init($("frame1"));
+
+ is(SpecialPowers.unwrap(walkerSubDocument.currentNode), $("frame1"), "Unexpected sub-doc root");
+ testFunc(walkerSubDocument, "firstChild", $("frame1").contentDocument,
+ "step to sub document");
+ testFunc(walkerSubDocument, "firstChild", $("frame1").contentDocument.doctype,
+ "step to sub documents doctype");
+ testFunc(walkerSubDocument, "nextSibling", $("frame1").contentDocument.documentElement,
+ "step to sub documents documentElement");
+
+ walkerSubDocument.currentNode = $("frame1").contentDocument;
+ is(SpecialPowers.unwrap(walkerSubDocument.currentNode), $("frame1").contentDocument,
+ "setting currentNode to sub document");
+ testFunc(walkerSubDocument, "nextSibling", null,
+ "nextSibling for sub document is null");
+
+ var walkerFrameChild =
+ SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(SpecialPowers.Ci.inIDeepTreeWalker);
+ walkerFrameChild.showAnonymousContent = false;
+ walkerFrameChild.showSubDocuments = false;
+ walkerFrameChild.init($("frame1"));
+
+ is(SpecialPowers.unwrap(walkerFrameChild.currentNode), $("frame1"), "Unexpected sub-doc root");
+ testFunc(walkerFrameChild, "firstChild", $("frame1").firstChild,
+ "step to frames child");
+
+ var walkerNonAnon =
+ SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(SpecialPowers.Ci.inIDeepTreeWalker);
+ walkerNonAnon.init($("display"));
+ walkerNonAnon.showAnonymousContent = false;
+
+ is(SpecialPowers.unwrap(walkerNonAnon.currentNode), $("display"), "Unexpected non-anon root");
+ testFunc(walkerNonAnon, "nextNode", $("s").previousSibling,
+ "step to some text");
+ testFunc(walkerNonAnon, "nextNode", $("s"), "step to span");
+ testFunc(walkerNonAnon, "nextNode", $("s").firstChild, "step to span text");
+ testFunc(walkerNonAnon, "nextNode", $("s").nextSibling, "step to more text");
+ testFunc(walkerNonAnon, "nextNode", $("b"), "step to bold");
+ testFunc(walkerNonAnon, "nextNode", $("b").firstChild, "step to bold text");
+ testFunc(walkerNonAnon, "nextNode", $("i1"), "step to first italic");
+ testFunc(walkerNonAnon, "nextNode", $("i1").firstChild,
+ "step to first italic text");
+ testFunc(walkerNonAnon, "nextNode", $("i1").nextSibling,
+ "step to more bold text");
+ testFunc(walkerNonAnon, "nextNode", $("i2"), "step to second italic");
+ testFunc(walkerNonAnon, "nextNode", $("i2").firstChild,
+ "step to second italic text");
+ testFunc(walkerNonAnon, "nextNode", null, "step past end");
+ testFunc(walkerNonAnon, "parentNode", $("i2"), "step up to second italic");
+ testFunc(walkerNonAnon, "parentNode", $("b"), "step up to bold");
+ testFunc(walkerNonAnon, "nextNode", $("b").firstChild, "step to bold text again");
+ testFunc(walkerNonAnon, "parentNode", $("b"), "step up to bold again");
+ testFunc(walkerNonAnon, "parentNode", $("display"), "step up to display");
+ testFunc(walkerNonAnon, "parentNode", null, "step up past root");
+ testFunc(walkerNonAnon, "firstChild", $("s").previousSibling,
+ "step firstChild to display first child");
+ testFunc(walkerNonAnon, "nextSibling", $("s"),
+ "step nextSibling to span");
+ testFunc(walkerNonAnon, "nextSibling", $("s").nextSibling,
+ "step nextSibling to more text");
+ testFunc(walkerNonAnon, "nextSibling", $("b"), "step nextSibling to bold");
+ testFunc(walkerNonAnon, "nextSibling", null, "step nextSibling past end");
+ testFunc(walkerNonAnon, "previousSibling", $("s").nextSibling,
+ "step previousSibling to more text");
+ testFunc(walkerNonAnon, "previousSibling", $("s"),
+ "step previousSibling to span");
+ testFunc(walkerNonAnon, "previousSibling", $("s").previousSibling,
+ "step previousSibling to display first child");
+ testFunc(walkerNonAnon, "previousSibling", null,
+ "step previousSibling past end");
+
+ // Move the walker over to the end
+ while (walkerNonAnon.nextNode()) { /* do nothing */ }
+
+ is(SpecialPowers.unwrap(walkerNonAnon.currentNode), $("i2").firstChild, "unexpected last node");
+ testFunc(walkerNonAnon, "previousNode", $("i2"), "step back to second italic");
+ testFunc(walkerNonAnon, "previousNode", $("i1").nextSibling,
+ "step back to more bold text");
+ testFunc(walkerNonAnon, "previousNode", $("i1").firstChild,
+ "step back to first italic text");
+ testFunc(walkerNonAnon, "previousNode", $("i1"), "step back to first italic");
+ testFunc(walkerNonAnon, "previousNode", $("b").firstChild,
+ "step back to bold text");
+ testFunc(walkerNonAnon, "previousNode", $("b"), "step back to bold");
+ testFunc(walkerNonAnon, "previousNode", $("s").nextSibling, "step back to more text");
+ testFunc(walkerNonAnon, "previousNode", $("s").firstChild, "step back to span text");
+ testFunc(walkerNonAnon, "previousNode", $("s"), "step back to span");
+ testFunc(walkerNonAnon, "previousNode", $("s").previousSibling,
+ "step back to some text");
+ testFunc(walkerNonAnon, "previousNode", $("display"),
+ "step back to root");
+ testFunc(walkerNonAnon, "previousNode", null,
+ "step back past root");
+
+ walkerNonAnon.currentNode = $("s");
+ is(SpecialPowers.unwrap(walkerNonAnon.currentNode), SpecialPowers.unwrap($("s")),
+ "Setting currentNode to span");
+
+ var anonDiv = $("display").shadowRoot.children[0];
+
+ try {
+ walkerNonAnon.currentNode = anonDiv;
+ // See bug 1586916.
+ todo(false, "Setting current node to a node that is otherwise unreachable," +
+ " with the current visibility settings should throw");
+ } catch(e) {
+ ok(e.toString().indexOf("NS_ERROR_ILLEGAL_VALUE") > -1, "Setting current node to an anon node should throw" +
+ " NS_ERROR_ILLEGAL_VALUE if showAnonymousContent is set to false");
+ is(SpecialPowers.unwrap(walkerNonAnon.currentNode), SpecialPowers.unwrap($("s")),
+ "An unsuccessfull set currentNode should leave behind the old state");
+ testFunc(walkerNonAnon, "nextSibling", $("s").nextSibling, "nextSibling after set currentNode");
+ }
+
+ var slot = $("display").shadowRoot.querySelectorAll("slot")[0];
+ var namedSlot = $("display").shadowRoot.querySelectorAll("slot")[1];
+
+ var walkerAnon =
+ SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(SpecialPowers.Ci.inIDeepTreeWalker);
+ walkerAnon.showAnonymousContent = true;
+ walkerAnon.init($("display"));
+
+ is(SpecialPowers.unwrap(walkerAnon.currentNode), $("display"), "Unexpected anon root");
+ testFunc(walkerAnon, "nextNode", $("display").shadowRoot,
+ "step to shadow root");
+ testFunc(walkerAnon, "nextNode", anonDiv,
+ "step to anonymous div");
+ testFunc(walkerAnon, "nextNode", slot, "step into slot");
+ testFunc(walkerAnon, "nextNode", namedSlot, "step into named slot");
+ testFunc(walkerAnon, "nextNode", $("s").previousSibling, "step to light dom text (out of shadow tree)");
+ testFunc(walkerAnon, "nextNode", $("s"), "step to span (anon)");
+ testFunc(walkerAnon, "nextNode", $("s").firstChild, "step to span text (anon)");
+ testFunc(walkerAnon, "nextNode", $("s").nextSibling, "step to more text (anon)");
+ testFunc(walkerAnon, "nextNode", $("b"), "step to bold (anon)");
+ testFunc(walkerAnon, "nextNode", $("b").firstChild, "step to bold text (anon)");
+ testFunc(walkerAnon, "nextNode", $("i1"), "step to first italic (anon)");
+ testFunc(walkerAnon, "nextNode", $("i1").firstChild,
+ "step to first italic text (anon)");
+ testFunc(walkerAnon, "nextNode", $("i1").nextSibling,
+ "step to more bold text (anon)");
+ testFunc(walkerAnon, "nextNode", $("i2"), "step to second italic (anon)");
+ testFunc(walkerAnon, "nextNode", $("i2").firstChild,
+ "step to second italic text (anon)");
+ testFunc(walkerAnon, "nextNode", null, "step past end (anon)");
+ testFunc(walkerAnon, "parentNode", $("i2"), "step up to italic (anon)");
+ testFunc(walkerAnon, "parentNode", $("b"), "step up to bold (anon)");
+ testFunc(walkerAnon, "parentNode", $("display"), "step up to display (anon)");
+ testFunc(walkerAnon, "nextNode", $("display").shadowRoot, "step to shadow root again");
+ testFunc(walkerAnon, "parentNode", $("display"),
+ "step up to display again (anon)")
+ testFunc(walkerAnon, "parentNode", null, "step up past root (anon)");
+ testFunc(walkerAnon, "firstChild", $("display").shadowRoot,
+ "step firstChild to display first child (anon)");
+ testFunc(walkerAnon, "nextSibling", $("s").previousSibling,
+ "step nextSibling text (anon)");
+ testFunc(walkerAnon, "nextSibling", $("s"),
+ "step nextSibling span (anon)");
+ testFunc(walkerAnon, "nextSibling", $("s").nextSibling,
+ "step nextSibling more text (anon)");
+ testFunc(walkerAnon, "nextSibling", $("b"),
+ "step nextSibling bold (anon)");
+ testFunc(walkerAnon, "nextSibling", null, "step nextSibling past end (anon)");
+ testFunc(walkerAnon, "previousSibling", $("s").nextSibling,
+ "step previousSibling to more text");
+ testFunc(walkerAnon, "previousSibling", $("s"),
+ "step previousSibling to span");
+ testFunc(walkerAnon, "previousSibling", $("s").previousSibling,
+ "step previousSibling to prev text");
+ testFunc(walkerAnon, "previousSibling", $("display").shadowRoot,
+ "step shadowRoot");
+ testFunc(walkerAnon, "previousSibling", null, "step previousSibling past end (anon)");
+
+ // Move the walker over to the end
+ while (walkerAnon.nextNode()) { /* do nothing */ }
+
+ testFunc(walkerAnon, "previousNode", $("i2"), "step back to second italic (anon)");
+ testFunc(walkerAnon, "previousNode", $("i1").nextSibling,
+ "step back to more bold text (anon)");
+ testFunc(walkerAnon, "previousNode", $("i1").firstChild,
+ "step back to first italic text (anon)");
+ testFunc(walkerAnon, "previousNode", $("i1"), "step back to first italic (anon)");
+ testFunc(walkerAnon, "previousNode", $("b").firstChild, "step back to bold text (anon)");
+ testFunc(walkerAnon, "previousNode", $("b"), "step back to bold (anon)");
+ testFunc(walkerAnon, "previousNode", $("s").nextSibling, "step back to more text (anon)");
+ testFunc(walkerAnon, "previousNode", $("s").firstChild,
+ "step back to span text (anon)");
+ testFunc(walkerAnon, "previousNode", $("s"),
+ "step back to span (anon)");
+ testFunc(walkerAnon, "previousNode", $("s").previousSibling,
+ "step back to some text (anon)");
+ testFunc(walkerAnon, "previousNode", namedSlot, "step back to named slot");
+ testFunc(walkerAnon, "previousNode", slot, "step back to slot");
+ testFunc(walkerAnon, "previousNode", anonDiv,
+ "step back to anonymous div");
+ testFunc(walkerAnon, "previousNode", $("display").shadowRoot, "step back to shadow root (anon)");
+ testFunc(walkerAnon, "previousNode", $("display"), "step back to root (anon)");
+ testFunc(walkerAnon, "previousNode", null, "step back past root (anon)");
+
+ SimpleTest.finish();
+});
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug536379-2.html b/layout/inspector/tests/test_bug536379-2.html
new file mode 100644
index 0000000000..265f464363
--- /dev/null
+++ b/layout/inspector/tests/test_bug536379-2.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=536379
+-->
+<head>
+ <title>Test for Bug 536379</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" type="text/css" href="data:text/css,@import 'data:text/css,p { color: green }'">
+ <link rel="stylesheet" type="text/css" href="data:text/css,@import &quot;data:text/css,@import 'data:text/css,p { color: green }'&quot;">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=536379">Mozilla Bug 536379</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 536379 **/
+
+const CI = SpecialPowers.Ci;
+const CC = SpecialPowers.Cc;
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+var rules = InspectorUtils.getCSSStyleRules(document.getElementById("display"));
+var firstPRule = rules[rules.length - 2];
+firstPRule.style.removeProperty("color");
+ok(true, "should not crash");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug536379.html b/layout/inspector/tests/test_bug536379.html
new file mode 100644
index 0000000000..ae9e1b961c
--- /dev/null
+++ b/layout/inspector/tests/test_bug536379.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=536379
+-->
+<head>
+ <title>Test for Bug 536379</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" type="text/css" href="data:text/css,p { color: green }">
+ <link rel="stylesheet" type="text/css" href="data:text/css,p { color: green }">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=536379">Mozilla Bug 536379</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 536379 **/
+
+const CI = SpecialPowers.Ci;
+const CC = SpecialPowers.Cc;
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+var rules = InspectorUtils.getCSSStyleRules(document.getElementById("display"));
+var firstPRule = rules[rules.length - 2];
+firstPRule.style.removeProperty("color");
+ok(true, "should not crash");
+
+var links = document.getElementsByTagName("link");
+is(links.length, 3, "links.length");
+is(SpecialPowers.unwrap(firstPRule.parentStyleSheet), links[1].sheet, "sheet match for first P rule");
+var secondPRule = rules[rules.length - 1];
+is(SpecialPowers.unwrap(secondPRule.parentStyleSheet), links[2].sheet, "sheet match for second P rule");
+is(links[1].href, links[2].href, "links should have same href");
+isnot(links[1].sheet, links[2].sheet, "links should have different sheets");
+isnot(firstPRule, secondPRule, "rules should be different");
+isnot(firstPRule.cssText, secondPRule.cssText, "text should be different since property was removed from one");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug557726.html b/layout/inspector/tests/test_bug557726.html
new file mode 100644
index 0000000000..bb3cefc504
--- /dev/null
+++ b/layout/inspector/tests/test_bug557726.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557726
+-->
+<head>
+ <title>Test for Bug 557726</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="pseudo-style">
+ #div1 {
+ color: blue;
+ }
+
+ #div1:first-letter {
+ font-weight: bold;
+ }
+
+ #div1:before {
+ content: '"';
+ }
+
+ #div1:after {
+ content: '"';
+ }
+
+ #div1:after, #div1:before {
+ color: red;
+ }
+
+ ::selection {
+ background: pink;
+ }
+
+ ::highlight(search) {
+ background: yellow;
+ }
+
+ ::highlight(whatever) {
+ background: tomato;
+ }
+ </style>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557726">Mozilla Bug 557726</a>
+
+<div id="div1">
+ text with ::before, ::after, and ::first-letter pseudo-elements
+</div>
+
+<script type="application/javascript">
+
+/** Test for Bug 557726 **/
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function getSelectors (rules) {
+ var styleElement = document.getElementById("pseudo-style");
+ var selectors = [];
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i];
+ if (SpecialPowers.unwrap(rule.parentStyleSheet.ownerNode) == styleElement) // no user agent rules
+ selectors.push(rule.selectorText);
+ }
+ return selectors;
+}
+
+var div = document.getElementById("div1");
+
+/* empty or missing pseudo-element argument */
+var selectors = getSelectors(InspectorUtils.getCSSStyleRules(div));
+is(selectors.length, 1, "psuedo-element argument should be optional");
+is(selectors[0], "#div1", "should only have the non-pseudo element rule");
+
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, ""));
+is(selectors.length, 1, "pseudo-element argument can be empty string");
+is(selectors[0], "#div1", "should only have the non pseudo-element rule");
+
+
+/* invalid pseudo-element argument */
+var rules = InspectorUtils.getCSSStyleRules(div, "not a valid pseudo element");
+is(rules.length, 0, "invalid pseudo-element returns no rules");
+
+
+/* valid pseudo-element argument */
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, ":first-letter"));
+is(selectors.length, 1, "pseudo-element argument can be used");
+is(selectors[0], "#div1::first-letter", "should only have the ::first-letter rule");
+
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, ":before"));
+is(selectors.length, 2, "::before pseudo-element has two matching rules");
+isnot(selectors.indexOf("#div1::after, #div1::before"), -1, "fetched rule for ::before")
+isnot(selectors.indexOf("#div1::before"), -1, "fetched rule for ::before")
+
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, ":first-line"));
+is(selectors.length, 0, "valid pseudo-element but no matching rules");
+
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, "::selection"));
+is(selectors.length, 1, "::selection pseudo-element has a matching rule");
+is(selectors[0], "::selection", "fetched rule for ::selection");
+
+const range = new Range();
+range.setStart(div.firstChild, 10);
+range.setEnd(div.firstChild, 20);
+const highlight = new Highlight(range);
+CSS.highlights.set("search", highlight);
+
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, "::highlight(search)"));
+is(selectors.length, 1, "::highlight(search) pseudo-element has a matching rule");
+is(selectors[0], "::highlight(search)", "fetched ::highlight(search) rule");
+
+selectors = getSelectors(InspectorUtils.getCSSStyleRules(div, "::highlight(whatever)"));
+is(selectors.length, 1, "::highlight(whatever) pseudo-element has a matching rule");
+is(selectors[0], "::highlight(whatever)", "fetched ::highlight(whatever) rule");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug609549-shadow.xhtml b/layout/inspector/tests/test_bug609549-shadow.xhtml
new file mode 100644
index 0000000000..230886e39d
--- /dev/null
+++ b/layout/inspector/tests/test_bug609549-shadow.xhtml
@@ -0,0 +1,79 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=609549
+-->
+<head>
+ <title>Test for Bug 609549</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+<!-- No linebreaks since this is html and whitespace is preserved. -->
+<template id="template"><div anonid="box-A">x</div><div anonid="box-B"><slot name="name-span"/></div><div anonid="box-C">x</div><slot/></template>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=609549">Mozilla Bug 609549</a>
+
+<custom-element id="bound"><p id="p">lorem ipsum dolor sit amet</p><span id="sandwiched" slot="name-span">sandwiched</span></custom-element>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+<![CDATA[
+
+/** Test for Bug 609549 **/
+SimpleTest.waitForExplicitFinish();
+
+customElements.define("custom-element", class extends HTMLElement {
+ constructor() {
+ super();
+ const template = document.getElementById("template");
+ const shadowRoot = this.attachShadow({mode: "open"})
+ .appendChild(template.content.cloneNode(true));
+ }
+});
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+addLoadEvent(function() {
+ ok("getChildrenForNode" in InspectorUtils, "InspectorUtils has no getChildrenForNode");
+ var withoutAnons =
+ InspectorUtils.getChildrenForNode($("bound"), false, false);
+
+ is(withoutAnons.length, $("bound").childNodes.length,
+ "withoutAnons should be the same length as childNodes");
+ is(SpecialPowers.unwrap(withoutAnons[0]), $("p"), "didn't get paragraph - without anons");
+ is(SpecialPowers.unwrap(withoutAnons[1]), $("sandwiched"),
+ "didn't get sandwiched span - without anons");
+
+ var withAnons = InspectorUtils.getChildrenForNode($("bound"), true, false);
+ is(withAnons.length, 3, "bad withAnons.length");
+ ok(SpecialPowers.unwrap(withAnons[0]) instanceof ShadowRoot, "First one is shadow");
+ is(SpecialPowers.unwrap(withAnons[1]), $("p"), "didn't get paragraph - without anons");
+ is(SpecialPowers.unwrap(withAnons[2]), $("sandwiched"),
+ "didn't get sandwiched span - without anons");
+
+ withAnons = InspectorUtils.getChildrenForNode(withAnons[0], true, false);
+ is(withAnons.length, 4, "bad withAnons.length");
+ is(withAnons[0].getAttribute("anonid"), "box-A",
+ "didn't get anonymous box-A");
+ is(withAnons[1].getAttribute("anonid"), "box-B",
+ "didn't get anonymous box-B");
+ is(withAnons[2].getAttribute("anonid"), "box-C",
+ "didn't get anonymous box-C");
+ is(withAnons[3].assignedNodes()[0].id, "p", "didn't get paragraph - with anons");
+
+ var bKids = InspectorUtils.getChildrenForNode(withAnons[1], true, false)[0].assignedNodes();
+ is(bKids.length, 1, "bKids.length is bad");
+ is(SpecialPowers.unwrap(bKids[0]), $("sandwiched"),
+ "didn't get sandwiched span inserted into box-B");
+
+ SimpleTest.finish();
+});
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug856317.html b/layout/inspector/tests/test_bug856317.html
new file mode 100644
index 0000000000..282919024a
--- /dev/null
+++ b/layout/inspector/tests/test_bug856317.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=856317
+-->
+<head>
+ <title>Test for Bug 856317</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" type="text/css" href="bug856317.css"/>
+ <style type="text/css" title="bug856317-inline">
+/* simplest possible */
+.alpha {
+}
+
+/* with leading whitespace */
+ #beta {
+ }
+
+/* with a comment before the rule */ #gamma {
+}
+
+/* mixed spaces and tab characters */
+ #delta {
+}
+
+/* long lines, like those produced by CSS minifiers
+ * (overflows a 16-bit unsigned int)
+ */
+ .epsilon{ background-image: url("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.png")}.zeta,.eta{}
+ </style>
+</head>
+<body>
+<script type="application/javascript">
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+var expectedResults = {
+ ".alpha": { line: 6, column: 1 },
+ "#beta": { line: 10, column: 5 },
+ "#gamma": { line: 13, column: 38 },
+ "#delta": { line: 17, column: 5 },
+ ".epsilon": { line: 23, column: 2 },
+ ".zeta, .eta": { line: 23, column: 65578 }
+};
+
+// tests are run on both the external stylesheet and the embedded stylesheet
+function executeTests(lineOffset, stylesheetPredicate) {
+ var rules;
+ for (var i=0; i < document.styleSheets.length; ++i) {
+ var styleSheet = document.styleSheets.item(i);
+ if (stylesheetPredicate(styleSheet)) {
+ rules = styleSheet.cssRules;
+ break;
+ }
+ }
+ if (!rules) {
+ ok(false, "stylesheet document not found");
+ return;
+ }
+
+ for (var i=0; i < rules.length; ++i) {
+ var rule = rules.item(i);
+ expected = expectedResults[rule.selectorText];
+ is(InspectorUtils.getRuleLine(rule), expected.line + lineOffset,
+ "line number for "+rule.selectorText);
+ is(InspectorUtils.getRuleColumn(rule), expected.column,
+ "column number for "+rule.selectorText);
+ }
+}
+
+// run tests on external stylesheet
+executeTests(0, function (styleSheet) {
+ return styleSheet.href.indexOf("bug856317.css") > 0;
+});
+
+// run tests on embedded stylesheet
+executeTests(7, function (styleSheet) {
+ return styleSheet.title == "bug856317-inline";
+});
+</script>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_bug877690.html b/layout/inspector/tests/test_bug877690.html
new file mode 100644
index 0000000000..0c01428176
--- /dev/null
+++ b/layout/inspector/tests/test_bug877690.html
@@ -0,0 +1,279 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877690
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for InspectorUtils.getCSSValuesForProperty</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+
+/** Test for InspectorUtils.getCSSValuesForProperty **/
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+// Returns true if values contains all and only the expected values. False otherwise.
+function testValues(values, expected) {
+ values.sort();
+ expected.sort();
+
+ info(JSON.stringify([...values]));
+ info(JSON.stringify(expected));
+
+ if (values.length !== expected.length) {
+ return false;
+ }
+
+ for (var i = 0; i < values.length; ++i) {
+ if (values[i] !== expected[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function do_test() {
+ var allColors = [
+ "COLOR",
+ "currentColor",
+ "transparent",
+ "rgb",
+ "rgba",
+ "hsl",
+ "hsla",
+ "hwb",
+ "color-mix",
+ "color",
+ "lab",
+ "lch",
+ "oklab",
+ "oklch"
+ ];
+
+ var allGradients = [
+ "linear-gradient",
+ "-webkit-linear-gradient",
+ "-moz-linear-gradient",
+ "repeating-linear-gradient",
+ "-webkit-repeating-linear-gradient",
+ "-moz-repeating-linear-gradient",
+ "radial-gradient",
+ "-webkit-radial-gradient",
+ "-moz-radial-gradient",
+ "repeating-radial-gradient",
+ "-webkit-repeating-radial-gradient",
+ "-moz-repeating-radial-gradient",
+ "-webkit-gradient",
+ "conic-gradient",
+ "repeating-conic-gradient",
+ ];
+
+ // test a property with keywords and colors
+ var prop = "color";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "inherit", "unset", "revert", "revert-layer", ...allColors ];
+ ok(testValues(values, expected), "property color's values.");
+
+ // test a shorthand property
+ var prop = "background";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "auto", "initial", "inherit", "unset", "revert", "revert-layer", ...allColors, "no-repeat", "repeat",
+ "repeat-x", "repeat-y", "space", "round", "fixed", "scroll", "local", "center", "top", "bottom", "left", "right",
+ "border-box", "padding-box", "content-box", "text", "contain",
+ "cover", "none", "-moz-element", "url", ...allGradients, "image-set" ];
+
+ if(SpecialPowers.getBoolPref("layout.css.cross-fade.enabled")) {
+ expected.push("cross-fade");
+ }
+
+ ok(testValues(values, expected), "property background values.");
+
+ var prop = "border";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "unset", "revert", "revert-layer", "dashed", "dotted", "double",
+ "groove", "hidden", "inherit", "inset", "medium", "none", "outset", "ridge",
+ "solid", "thick", "thin", ...allColors ]
+ ok(testValues(values, expected), "property border values.");
+
+ // test keywords only
+ var prop = "border-top";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "inherit", "revert", "revert-layer", "unset", "thin", "medium", "thick", "none", "hidden", "dotted",
+ "dashed", "solid", "double", "groove", "ridge", "inset", "outset", ...allColors ];
+ ok(testValues(values, expected), "property border-top's values.");
+
+ // tests no keywords or colors
+ var prop = "padding-bottom";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "inherit", "unset", "revert", "revert-layer" ];
+ ok(testValues(values, expected), "property padding-bottom's values.");
+
+ // test proprety
+ var prop = "display";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "inherit", "unset", "revert", "revert-layer", "none", "inline", "block", "inline-block", "list-item",
+ "table", "inline-table", "table-row-group", "table-header-group", "table-footer-group", "table-row",
+ "table-column-group", "table-column", "table-cell", "table-caption",
+ "flex", "inline-flex", "-webkit-box", "-webkit-inline-box",
+ "grid", "inline-grid", "inline list-item", "inline flow-root list-item", "flow-root list-item",
+ "ruby", "ruby-base", "ruby-base-container", "ruby-text", "ruby-text-container", "block ruby",
+ "contents", "flow-root" ];
+ ok(testValues(values, expected), "property display's values.");
+
+ // test property
+ var prop = "float";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "inherit", "unset", "revert", "revert-layer", "none", "left", "right", "inline-start", "inline-end" ];
+ ok(testValues(values, expected), "property float's values.");
+
+ // Test property with "auto"
+ var prop = "margin";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "unset", "auto", "inherit", "revert", "revert-layer" ];
+ ok(testValues(values, expected), "property margin's values.");
+
+ // Test property with "normal"
+ var prop = "font-style";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "inherit", "unset", "revert", "revert-layer", "italic", "normal", "oblique" ];
+ ok(testValues(values, expected), "property font-style's values.");
+
+ // Test property with "cubic-bezier" and "step".
+ var prop = "transition";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "initial", "all", "unset", "cubic-bezier", "ease", "ease-in", "ease-in-out",
+ "ease-out", "inherit", "revert", "revert-layer", "linear", "none", "step-end", "step-start",
+ "steps" ];
+ ok(testValues(values, expected), "property transition's values.");
+
+ // test invalid property
+ var prop = "invalidProperty";
+ try {
+ InspectorUtils.getCSSValuesForProperty(prop);
+ ok(false, "invalid property should throw an exception");
+ }
+ catch(e) {
+ // test passed
+ }
+
+ // test border-image property, for bug 973345
+ var prop = "border-image";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "auto", "inherit", "revert", "revert-layer", "initial", "unset", "repeat", "stretch",
+ "-moz-element", "url", ...allGradients,
+ "fill", "none", "round", "space", "image-set" ];
+
+ if(SpecialPowers.getBoolPref("layout.css.cross-fade.enabled")) {
+ expected.push("cross-fade");
+ }
+
+ ok(testValues(values, expected), "property border-image's values.");
+
+ var prop = "background-size"
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "auto", "inherit", "revert", "revert-layer", "initial", "unset", "contain", "cover" ];
+ ok(testValues(values, expected), "property background-size's values.");
+
+ // Regression test for bug 1255401.
+ var prop = "all"
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer" ];
+ ok(testValues(values, expected), "property all's values.");
+
+ var prop = "quotes"
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "auto", "none" ];
+ ok(testValues(values, expected), "property quotes's values.");
+
+ // Regression test for bug 1255384.
+ for (prop of ["counter-increment", "counter-reset"]) {
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ var expected = [ "inherit", "initial", "unset", "none", "revert", "revert-layer" ];
+ ok(testValues(values, expected), "property " + prop + "'s values.");
+ }
+
+ // Regression test for bug 1430616
+ var prop = "text-align";
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ ok(values.includes("match-parent"), "property text-align includes match-parent");
+
+ // Regression test for bug 1255402.
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "left", "right",
+ "top", "center", "bottom" ];
+ for (prop of ["object-position", "perspective-origin"]) {
+ var values = InspectorUtils.getCSSValuesForProperty(prop);
+ ok(testValues(values, expected), "property " + prop + "'s values");
+ }
+
+ // Regression test for bug 1255378.
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "none", ...allColors ];
+ var values = InspectorUtils.getCSSValuesForProperty("text-shadow");
+ ok(testValues(values, expected), "property text-shadow's values");
+
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "inset", "none", ...allColors ];
+ var values = InspectorUtils.getCSSValuesForProperty("box-shadow");
+ ok(testValues(values, expected), "property box-shadow's values");
+
+ // Regression test for bug 1255379.
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer",
+ "none", "url", "polygon", "circle", "ellipse", "inset",
+ "path", "rect", "xywh", "fill-box", "stroke-box", "view-box",
+ "margin-box", "border-box", "padding-box", "content-box" ];
+ var values = InspectorUtils.getCSSValuesForProperty("clip-path");
+ ok(testValues(values, expected), "property clip-path's values");
+
+ var expected = [ "inherit", "initial", "unset", "revert", "revert-layer", "auto", "rect" ];
+ var values = InspectorUtils.getCSSValuesForProperty("clip");
+ ok(testValues(values, expected), "property clip's values");
+
+ // Regression test for bug 1255380.
+ var expected = [ "normal", "none", "counter", "counters",
+ "attr", "open-quote", "close-quote", "no-open-quote",
+ "no-close-quote", "inherit", "initial", "unset", "revert",
+ "revert-layer", "-moz-alt-content", "-moz-label-content", "image-set", "url", "-moz-element" ]
+ .concat(allGradients);
+ var values = InspectorUtils.getCSSValuesForProperty("content");
+ ok(testValues(values, expected), "property content's values");
+
+ // Regression test for bug 1255369.
+ var expected = ["none", "decimal", "decimal-leading-zero", "arabic-indic", "armenian",
+ "upper-armenian", "lower-armenian", "bengali", "cambodian", "khmer",
+ "cjk-decimal", "devanagari", "georgian", "gujarati", "gurmukhi", "hebrew",
+ "kannada", "lao", "malayalam", "mongolian", "myanmar", "oriya", "persian",
+ "lower-roman", "upper-roman", "tamil", "telugu", "thai", "tibetan",
+ "lower-alpha", "lower-latin", "upper-alpha", "upper-latin",
+ "cjk-earthly-branch", "cjk-heavenly-stem", "lower-greek",
+ "hiragana", "hiragana-iroha", "katakana", "katakana-iroha",
+ "disc", "circle", "square", "disclosure-open", "disclosure-closed",
+ "japanese-informal", "japanese-formal",
+ "korean-hangul-formal", "korean-hanja-informal", "korean-hanja-formal",
+ "simp-chinese-informal", "simp-chinese-formal",
+ "trad-chinese-informal", "trad-chinese-formal", "cjk-ideographic",
+ "ethiopic-numeric", "symbols", "inherit", "initial", "unset", "revert", "revert-layer" ];
+ var values = InspectorUtils.getCSSValuesForProperty("list-style-type");
+ ok(testValues(values, expected), "property list-style-type's values");
+
+ // Regression test for bug 1696677.
+ var values = InspectorUtils.getCSSValuesForProperty("cursor");
+ ok(values.includes("url"), "property cursor values include url");
+ ok(values.includes("image-set"), "property cursor values include image-set");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877690">Mozilla Bug 877690</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_color_to_rgba.html b/layout/inspector/tests/test_color_to_rgba.html
new file mode 100644
index 0000000000..96952ad8f2
--- /dev/null
+++ b/layout/inspector/tests/test_color_to_rgba.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::ColorToRGBA</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ testColor("red", {r:255, g:0, b:0, a:1});
+ testColor("#f00", {r:255, g:0, b:0, a:1});
+ testColor("#ff0000", {r:255, g:0, b:0, a:1});
+ testColor("ff0000", null);
+ testColor("rgb(255,0,0)", {r:255, g:0, b:0, a:1});
+ testColor("rgba(255,0,0)", {r:255, g:0, b:0, a:1});
+ testColor("rgb(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
+ testColor("rgba(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
+ testColor("rgb(50%,75%,60%)", {r:128, g:191, b:153, a:1});
+ testColor("rgba(50%,75%,60%)", {r:128, g:191, b:153, a:1});
+ testColor("rgb(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
+ testColor("rgba(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
+ testColor("hsl(320,30%,10%)", {r:33, g:18, b:28, a:1});
+ testColor("hsla(320,30%,10%)", {r:33, g:18, b:28, a:1});
+ testColor("hsl(170,60%,40%,0.9)", {r:41, g:163, b:143, a:0.9});
+ testColor("hsla(170,60%,40%,0.9)", {r:41, g:163, b:143, a:0.9});
+
+ // NOTE: LightweightThemeConsumer is the only consumer of this API, for
+ // backwards compat reasons... But it doesn't seem like it'd really need to
+ // care about system colors, so maybe we can remove it in the future.
+ isnot(
+ InspectorUtils.colorToRGBA("ButtonText", document),
+ null,
+ "Should support system colors when given a displayed document"
+ );
+
+ function testColor(color, expected) {
+ let rgb = InspectorUtils.colorToRGBA(color);
+
+ if (rgb === null) {
+ ok(expected === null, "color: " + color + " returns null");
+ return;
+ }
+
+ let {r, g, b, a} = rgb;
+
+ is(r, expected.r, "color: " + color + ", red component is converted correctly");
+ is(g, expected.g, "color: " + color + ", green component is converted correctly");
+ is(b, expected.b, "color: " + color + ", blue component is converted correctly");
+ is(Math.round(a * 10) / 10, expected.a, "color: " + color + ", alpha component is a converted correctly");
+ }
+ </script>
+</head>
+<body>
+<h1>Test InspectorUtils::ColorToRGBA</h1>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_containing_block_of.html b/layout/inspector/tests/test_containing_block_of.html
new file mode 100644
index 0000000000..e9f0414755
--- /dev/null
+++ b/layout/inspector/tests/test_containing_block_of.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test InspectorUtils.containingBlockOf</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ #positioned-grid {
+ display: grid;
+ position: relative;
+ }
+ .abs {
+ position: absolute;
+ }
+ .fixed {
+ position: fixed;
+ }
+</style>
+<div id="positioned-grid">
+ <div class="item">
+ <div class="abs"></div>
+ <div class="fixed"></div>
+ </div>
+</div>
+<script>
+const InspectorUtils = SpecialPowers.InspectorUtils;
+const abs = document.querySelector(".abs");
+const fixed = document.querySelector(".fixed");
+const grid = document.querySelector("#positioned-grid");
+is(InspectorUtils.containingBlockOf(fixed), null, "CB of fixed pos is the viewport");
+is(SpecialPowers.unwrap(InspectorUtils.containingBlockOf(abs)), grid, "CB of abspos is the grid container");
+const item = document.querySelector(".item");
+item.style.willChange = "transform";
+is(SpecialPowers.unwrap(InspectorUtils.containingBlockOf(fixed)), item, "Transformed element is the cb of fixed pos elements");
+is(SpecialPowers.unwrap(InspectorUtils.containingBlockOf(abs)), item, "Transformed element is the cb of abspos elements");
+</script>
diff --git a/layout/inspector/tests/test_css_property_is_shorthand.html b/layout/inspector/tests/test_css_property_is_shorthand.html
new file mode 100644
index 0000000000..7fe55563b3
--- /dev/null
+++ b/layout/inspector/tests/test_css_property_is_shorthand.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::CssPropertyIsShorthand</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ let tests = [
+ {
+ property: "color",
+ expected: false
+ },
+ {
+ property: "background",
+ expected: true
+ },
+ {
+ property: "--anything",
+ expected: false
+ }
+ ];
+
+ for (let {property, expected} of tests) {
+ let result = InspectorUtils.cssPropertyIsShorthand(property);
+ is(result, expected, "checking whether " + property + " is shorthand");
+ }
+
+ let sawException = false;
+ try {
+ let result = InspectorUtils.cssPropertyIsShorthand("nosuchproperty");
+ } catch (e) {
+ sawException = true;
+ }
+ ok(sawException, "checking whether nosuchproperty throws");
+
+ </script>
+</head>
+<body>
+<h1>Test InspectorUtils::CssPropertyIsShorthand</h1>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_getCSSPseudoElementNames.html b/layout/inspector/tests/test_getCSSPseudoElementNames.html
new file mode 100644
index 0000000000..255f58b889
--- /dev/null
+++ b/layout/inspector/tests/test_getCSSPseudoElementNames.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::getCSSPseudoElementNames</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript">
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ // NOTE(emilio): When this changes, make sure to update _pseudoIsRelevant in devtools/server/actors/styles.js
+ let expected = new Set([
+ "::after",
+ "::before",
+ "::marker",
+ "::backdrop",
+ "::cue",
+ "::file-selector-button",
+ "::first-letter",
+ "::first-line",
+ "::highlight",
+ "::placeholder",
+ "::selection",
+ "::-moz-color-swatch",
+ "::-moz-focus-inner",
+ "::-moz-meter-bar",
+ "::-moz-placeholder",
+ "::-moz-progress-bar",
+ "::-moz-range-progress",
+ "::-moz-range-thumb",
+ "::-moz-range-track",
+ ]);
+
+ let names = InspectorUtils.getCSSPseudoElementNames();
+ for (let name of names) {
+ ok(expected.has(name), name + " is included");
+ expected.delete(name);
+ }
+
+ if (expected.size > 0) {
+ todo_is(expected.size, 0,
+ "ideally all pseudo-element names would be listed in this test");
+ for (let extra of expected) {
+ info("extra element: " + extra);
+ }
+ }
+
+ </script>
+</head>
+
+<body>
+ <h1>Test InspectorUtils::getCSSPseudoElementNames</h1>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+
+ </div>
+ <pre id="test">
+</pre>
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/inspector/tests/test_getCSSStyleRules.html b/layout/inspector/tests/test_getCSSStyleRules.html
new file mode 100644
index 0000000000..e504c3294b
--- /dev/null
+++ b/layout/inspector/tests/test_getCSSStyleRules.html
@@ -0,0 +1,204 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 1359217</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<iframe id="test"></iframe>
+<pre id="log">
+<script>
+/**
+ * This test checks that getCSSStyleRules returns correct style set in
+ * various cases. To avoid effects from UA sheets, most of the tests use
+ * an element with "unknowntagname".
+ */
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+let iframe = document.getElementById("test");
+
+SimpleTest.waitForExplicitFinish();
+
+function* getStyleRules(elem) {
+ let rules = InspectorUtils.getCSSStyleRules(elem);
+ for (let i = 0; i < rules.length; i++) {
+ yield rules[i];
+ }
+}
+
+// This will check that value of z-index property declarations in the
+// rules from getCSSStyleRules matches the given content.
+function checkRules(doc, rulesContent, queryStr = "unknowntagname") {
+ let elem = doc.querySelector(queryStr);
+ let rules = [...getStyleRules(elem)];
+ is(rules.length, rulesContent.length, "Rule length should match");
+ if (rules.length != rulesContent.length) {
+ return;
+ }
+ for (let i = 0; i < rules.length; i++) {
+ let style = rules[i].style;
+ let expectation = rulesContent[i].toString();
+ is(style.length, 1, "Should contain only one declaration");
+ is(style.zIndex, expectation, "Should match expectation");
+ }
+}
+
+const tests = [
+ {
+ title: "Add new stylesheet",
+ async run(doc) {
+ checkRules(doc, [1]);
+ let link = doc.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "getCSSStyleRules-2.css";
+ let load = new Promise(resolve => { link.onload = resolve; });
+ doc.head.appendChild(link);
+ await load;
+ checkRules(doc, [1, 2]);
+ },
+ },
+ {
+ title: "Remove stylesheet",
+ async run(doc) {
+ checkRules(doc, [1]);
+ doc.querySelector("link").remove();
+ checkRules(doc, []);
+ },
+ },
+ {
+ title: "Enable stylesheet",
+ async run(doc) {
+ // Set disabled flag before we invoke the utils.
+ let link = doc.querySelector("link");
+ link.sheet.disabled = true;
+ checkRules(doc, []);
+ link.sheet.disabled = false;
+ checkRules(doc, [1]);
+ },
+ },
+ {
+ title: "Disable stylesheet",
+ async run(doc) {
+ checkRules(doc, [1]);
+ doc.querySelector("link").sheet.disabled = true;
+ checkRules(doc, []);
+ },
+ },
+ {
+ title: "Change stylesheet set",
+ base: "alternate",
+ async run(doc) {
+ checkRules(doc, []);
+ doc.selectedStyleSheetSet = "x";
+ checkRules(doc, [1]);
+ doc.selectedStyleSheetSet = "";
+ checkRules(doc, []);
+ },
+ },
+ {
+ title: "Add and remove rules",
+ async run(doc) {
+ checkRules(doc, [1]);
+
+ let sheet = doc.querySelector("link").sheet;
+ info("Inserting style rule");
+ sheet.insertRule("unknowntagname { z-index: 3; }", 1);
+ checkRules(doc, [1, 3]);
+
+ info("Removing style rule");
+ sheet.deleteRule(0);
+ checkRules(doc, [3]);
+
+ info("Inserting media rule");
+ sheet.insertRule("@media all { unknowntagname { z-index: 4; } }", 1);
+ checkRules(doc, [3, 4]);
+
+ info("Inserting supports rule");
+ sheet.insertRule(
+ "@supports (z-index: 0) { unknowntagname { z-index: 5; } }", 1);
+ checkRules(doc, [3, 5, 4]);
+
+ info("Inserting import rule");
+ sheet.insertRule("@import url(getCSSStyleRules-2.css);", 0);
+ // There is no notification we can get when the associated style
+ // sheet gets loaded, so we have to query it.
+ while (true) {
+ try {
+ sheet.cssRules[0].styleSheet.cssRules;
+ break;
+ } catch (e) {
+ if (e.name == "InvalidAccessError") {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ } else {
+ throw e;
+ }
+ }
+ }
+ checkRules(doc, [2, 3, 5, 4]);
+
+ info("Removing supports rule");
+ sheet.deleteRule(2);
+ checkRules(doc, [2, 3, 4]);
+
+ info("Removing media rule");
+ sheet.deleteRule(2);
+ checkRules(doc, [2, 3]);
+
+ info("Removing import rule");
+ sheet.deleteRule(0);
+ checkRules(doc, [3]);
+ },
+ },
+ {
+ title: "Check UA sheets",
+ async run(doc) {
+ doc.querySelector("link").remove();
+ checkRules(doc, []);
+ let elem = doc.querySelector("unknowntagname");
+ elem.setAttribute("dir", "");
+ let seenUnicodeBidi = false;
+ for (let rule of getStyleRules(elem)) {
+ if (rule.style.unicodeBidi == "isolate") {
+ seenUnicodeBidi = true;
+ break;
+ }
+ }
+ ok(seenUnicodeBidi, "Should have unicode-bidi " +
+ "declaration from UA stylesheet html.css");
+ },
+ },
+ {
+ title: "Check adopted sheets",
+ async run(doc, win) {
+ checkRules(doc, [1]);
+ let sheet = new win.CSSStyleSheet();
+ sheet.replaceSync(`unknowntagname { z-index: 5 }`);
+ doc.adoptedStyleSheets.push(sheet);
+ checkRules(doc, [1, 5]);
+ },
+ },
+];
+
+add_task(async function runTests() {
+ for (let i = 0; i < tests.length; i++) {
+ let test = tests[i];
+ info(`Test ${i}: ${test.title}`);
+ iframe.src = "about:blank";
+ if (!test.base) {
+ test.base = "default";
+ }
+ iframe.src = `file_getCSSStyleRules-${test.base}.html`;
+ await new Promise(resolve => { iframe.onload = resolve; });
+ try {
+ await test.run(iframe.contentDocument, iframe.contentWindow);
+ } catch (e) {
+ ok(false, "JavaScript error: " + e);
+ }
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_getCSSStyleRules_pseudo.html b/layout/inspector/tests/test_getCSSStyleRules_pseudo.html
new file mode 100644
index 0000000000..097a66baed
--- /dev/null
+++ b/layout/inspector/tests/test_getCSSStyleRules_pseudo.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<title>Test getCSSStyleRules for pseudo elements</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+
+<style>
+#block:before {
+ display: block;
+ content: ":before";
+}
+#block:after {
+ display: block;
+ content: ":after";
+}
+
+#table:before {
+ display: table;
+ content: ":before";
+}
+#table:after {
+ display: table;
+ content: ":after";
+}
+
+#flex:before {
+ display: flex;
+ content: ":before";
+}
+#flex:after {
+ display: flex;
+ content: ":after";
+}
+
+#grid:before {
+ display: grid;
+ content: ":before";
+}
+#grid:after {
+ display: grid;
+ content: ":after";
+}
+</style>
+
+<div id="block">block pseudos</div>
+<div id="table">table pseudos</div>
+<div id="flex">flex pseudos</div>
+<div id="grid">grid pseudos</div>
+
+<script>
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function checkPseudoStyleForId(id) {
+ let element = document.getElementById(id);
+
+ let beforeRules = InspectorUtils.getCSSStyleRules(element, ":before");
+ is (beforeRules.length, 1, "Element " + id + ":before has expected number of rules.");
+ let beforeDecl = beforeRules[0].style;
+ is (beforeDecl.content, '":before"', "Element " + id + ":before has expected style content.");
+
+ let afterRules = InspectorUtils.getCSSStyleRules(element, ":after");
+ is (afterRules.length, 1, "Element " + id + ":after has expected number of rules.");
+ let afterDecl = afterRules[0].style;
+ is (afterDecl.content, '":after"', "Element " + id + ":after has expected style content.");
+}
+
+let idsToCheck = [
+ "block",
+ "table",
+ "flex",
+ "grid",
+];
+
+for (let id of idsToCheck) {
+ checkPseudoStyleForId(id);
+}
+</script>
diff --git a/layout/inspector/tests/test_getCSSStyleRules_slotted.html b/layout/inspector/tests/test_getCSSStyleRules_slotted.html
new file mode 100644
index 0000000000..9fecb9bf5f
--- /dev/null
+++ b/layout/inspector/tests/test_getCSSStyleRules_slotted.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>Test getCSSStyleRules for pseudo elements</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+
+<div id="host">
+ <test-element id="test1" slot="slot1">test1</test-element>
+ <test-element id="test2" slot="slot2">test2</test-element>
+</div>
+
+<script>
+ const host = document.getElementById("host");
+ const shadow = host.attachShadow({ mode: "open" });
+ shadow.innerHTML = `
+ <style>
+ #outer ::slotted(test-element) {
+ color: red;
+ }
+ </style>
+ <div id="outer">
+ <slot name="slot1"></slot>
+ </div>
+ <div id="nested">
+ <slot name="slot2"></slot>
+ </div>
+ `;
+
+ const nested = shadow.querySelector("#nested").attachShadow({ mode: "open" });
+ nested.innerHTML = `
+ <style>
+ #inner ::slotted(test-element) {
+ font-weight: bold;
+ }
+ </style>
+ <div id="inner">
+ <slot></slot>
+ </div>
+ `;
+
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ function checkElementRules(id, text, property) {
+ const element = document.getElementById(id);
+ let slottedRules = InspectorUtils.getCSSStyleRules(element);
+ is(slottedRules.length, 1, "Slotted element has expected number of rules.");
+
+ let slottedText = slottedRules[0].cssText;
+ ok(slottedText.includes(text), "Slotted node has expected style content.");
+ ok(slottedText.includes(property), "Has expected property.");
+ }
+
+ info("Check that slotted rules are retrieved");
+ checkElementRules("test1", "#outer ::slotted(test-element)", "color");
+
+ info("Check that slotted rules are also retrieved for nested slots");
+ checkElementRules("test2", "#inner ::slotted(test-element)", "font-weight");
+</script>
diff --git a/layout/inspector/tests/test_getRegisteredCssHighlights.html b/layout/inspector/tests/test_getRegisteredCssHighlights.html
new file mode 100644
index 0000000000..87b79ef5d4
--- /dev/null
+++ b/layout/inspector/tests/test_getRegisteredCssHighlights.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test InspectorUtils.getRegisteredCssHighlights</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<code>InspectorUtils.getRegisteredCssHighlights</code>
+
+<script>
+"use strict";
+
+/** Test for InspectorUtils.getRegisteredCssHighlights **/
+
+const { Assert } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/Assert.sys.mjs"
+);
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+const codeEl = document.querySelector("code");
+const range = new Range();
+range.setStart(codeEl.firstChild, 10);
+range.setEnd(codeEl.firstChild, 20);
+const searchHighlight = new Highlight(range);
+CSS.highlights.set("search", searchHighlight);
+
+Assert.deepEqual(
+ InspectorUtils.getRegisteredCssHighlights(document),
+ ["search"],
+ `Got registered "search" highlight`
+);
+
+CSS.highlights.set("filter", new Highlight());
+
+Assert.deepEqual(
+ InspectorUtils.getRegisteredCssHighlights(document),
+ ["search", "filter"],
+ `Got new registered "filter" highlight and previous one`
+);
+
+Assert.deepEqual(
+ InspectorUtils.getRegisteredCssHighlights(document, true),
+ ["search"],
+ "Only got registered active highlights when passing activeOnly"
+);
+
+CSS.highlights.clear();
+
+is(
+ InspectorUtils.getRegisteredCssHighlights(document).length,
+ 0,
+ "Empty array returned when no highlights are registered"
+);
+is(
+ InspectorUtils.getRegisteredCssHighlights(document, true).length,
+ 0,
+ "Empty array returned when passing activeOnly and no highlights are registered"
+);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_getRegisteredCustomProperties.html b/layout/inspector/tests/test_getRegisteredCustomProperties.html
new file mode 100644
index 0000000000..10eaba992d
--- /dev/null
+++ b/layout/inspector/tests/test_getRegisteredCustomProperties.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test InspectorUtils.getCSSRegisteredProperties</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @property --color-1 {
+ syntax: "<color>";
+ inherits: true;
+ initial-value: blue;
+ }
+ @property --color-2 {
+ syntax: "*";
+ inherits: false;
+ }
+ </style>
+</head>
+<body>
+<code>InspectorUtils.getCSSRegisteredProperties</code>
+
+<script>
+"use strict";
+
+/** Test for InspectorUtils.getCSSRegisteredProperties **/
+
+const { Assert } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/Assert.sys.mjs"
+);
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+CSS.registerProperty({
+ name: "--length-1",
+ syntax: "<length>",
+ initialValue: "10px",
+ inherits: true,
+});
+CSS.registerProperty({
+ name: "--length-2",
+ syntax: "foo | <integer>+ | <percentage> | <length># | auto",
+ initialValue: "100vw",
+ inherits: true
+});
+CSS.registerProperty({
+ name: "--length-3",
+ syntax: "*",
+ inherits: false
+});
+CSS.registerProperty({
+ name: "--length-4",
+ syntax: "*",
+ initialValue: "",
+ inherits: false
+});
+
+// The order isn't guaranteed, so sort variable by their name.
+// We get a Proxy, so build another array to properly sort it.
+const results = Array.from(InspectorUtils.getCSSRegisteredProperties(document));
+results.sort((a,b) => a.name < b.name ? -1 : 1)
+
+Assert.deepEqual(
+ results,
+ [{
+ name: "--color-1",
+ syntax: "<color>",
+ inherits: true,
+ initialValue: "blue",
+ fromJS: false,
+ },{
+ name: "--color-2",
+ syntax: "*",
+ inherits: false,
+ initialValue: null,
+ fromJS: false,
+ },{
+ name: "--length-1",
+ syntax: "<length>",
+ inherits: true,
+ initialValue: "10px",
+ fromJS: true,
+ }, {
+ name: "--length-2",
+ syntax: "foo | <integer>+ | <percentage> | <length># | auto",
+ inherits: true,
+ initialValue: "100vw",
+ fromJS: true,
+ }, {
+ name: "--length-3",
+ syntax: "*",
+ inherits: false,
+ initialValue: null,
+ fromJS: true,
+ }, {
+ name: "--length-4",
+ syntax: "*",
+ inherits: false,
+ initialValue: "",
+ fromJS: true,
+ }],
+ `Got registered CSS properties`
+);
+
+// Test needs at least one `ok/is` call
+ok(true, "Success!")
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_getRelativeRuleLine.html b/layout/inspector/tests/test_getRelativeRuleLine.html
new file mode 100644
index 0000000000..9297cce98a
--- /dev/null
+++ b/layout/inspector/tests/test_getRelativeRuleLine.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::getRelativeRuleLine</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @supports (not (whatever: 72 zq)) {
+ #test {
+ background-color: #f0c;
+ }
+ }
+
+ #test {
+ color: #f0c;
+ }
+ </style>
+ <style>#test { color: red; }</style>
+ <style>
+ @invalidatkeyword {
+ }
+
+ #test {
+ color: blue;
+ }
+ </style>
+ <style>#test { color: red; }</style><!-- This tests stylesheet caching -->
+ <script>
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ let tests = [
+ { sheetNo: 0, ruleNo: 0, lineNo: 1, columnNo: 1 },
+ { sheetNo: 1, ruleNo: 0, lineNo: 2, columnNo: 5 },
+ { sheetNo: 1, ruleNo: 1, lineNo: 8, columnNo: 5 },
+ { sheetNo: 2, ruleNo: 0, lineNo: 1, columnNo: 1 },
+ { sheetNo: 3, ruleNo: 0, lineNo: 5, columnNo: 6 },
+ { sheetNo: 4, ruleNo: 0, lineNo: 1, columnNo: 1 },
+ ];
+
+ function doTest() {
+ for (let test of tests) {
+ let sheet = document.styleSheets[test.sheetNo];
+ let rule = sheet.cssRules[test.ruleNo];
+ let line = InspectorUtils.getRelativeRuleLine(rule);
+ let column = InspectorUtils.getRuleColumn(rule);
+ info("testing sheet " + test.sheetNo + ", rule " + test.ruleNo);
+ is(line, test.lineNo, "line number is correct");
+ is(column, test.columnNo, "column number is correct");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+ </script>
+</head>
+<body>
+<h1>Test InspectorUtils::getRelativeRuleLine</h1>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_getRuleIndex.html b/layout/inspector/tests/test_getRuleIndex.html
new file mode 100644
index 0000000000..0eb9dba27b
--- /dev/null
+++ b/layout/inspector/tests/test_getRuleIndex.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test InspectorUtils.getRuleIndex</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @property --color {
+ syntax: "*";
+ inherits: false;
+ }
+ html {
+ background: gold;
+
+ &:hover {
+ background: yellow;
+ }
+
+ & body {
+ color: tomato;
+
+ &:focus-visible {
+ outline: 1px solid cyan;
+ }
+ }
+ }
+ @keyframes anim {
+ 0% {
+ color: blue;
+ }
+ 100% {
+ color: green;
+ }
+ }
+ </style>
+</head>
+<body>
+<code>InspectorUtils.getRuleIndex</code>
+
+<script>
+"use strict";
+
+/** Test for InspectorUtils.getCSSRegisteredProperties **/
+
+const { Assert } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/Assert.sys.mjs"
+);
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+const stylesheet = document.styleSheets[1];
+const stylesheetRules = stylesheet.cssRules;
+
+let rule = stylesheetRules[0];
+is(rule.name, "--color", "Expected @property rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [0]);
+
+rule = stylesheetRules[1];
+is(rule.selectorText, "html", "Expected htlm rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [1]);
+
+rule = stylesheetRules[1].cssRules[0];
+is(rule.selectorText, "&:hover", "Expected nested &:hover rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [1, 0]);
+
+rule = stylesheetRules[1].cssRules[1];
+is(rule.selectorText, "& body", "Expected nested & body rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [1, 1]);
+
+rule = stylesheetRules[1].cssRules[1].cssRules[0];
+is(rule.selectorText, "&:focus-visible", "Expected nested &:focus-visible rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [1, 1, 0]);
+
+rule = stylesheetRules[2];
+is(rule.name, "anim", "Expected @keyframes rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [2]);
+
+rule = stylesheetRules[2].cssRules[0];
+is(rule.keyText, "0%", "Expected 0% keyframe rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [2, 0]);
+
+rule = stylesheetRules[2].cssRules[1];
+is(rule.keyText, "100%", "Expected 100% keyframe rule");
+Assert.deepEqual(InspectorUtils.getRuleIndex(rule), [2, 1]);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_get_all_style_sheets.html b/layout/inspector/tests/test_get_all_style_sheets.html
new file mode 100644
index 0000000000..906c4e9e08
--- /dev/null
+++ b/layout/inspector/tests/test_get_all_style_sheets.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=734861
+-->
+<meta charset="utf-8">
+<title>Test for Bug 734861</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=734861">Mozilla Bug 734861</a>
+<div id="host"></div>
+<script>
+/** Test for Bug 734861 **/
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+add_task(async function() {
+ let sheet = new CSSStyleSheet();
+ await sheet.replace(`* { color: blue }`);
+
+ let host = document.querySelector("#host");
+ host.adoptedStyleSheets = [sheet, sheet];
+ document.adoptedStyleSheets.push(sheet);
+
+ let res = InspectorUtils.getAllStyleSheets(document);
+
+ let foundUA = false;
+ let adoptedCount = 0;
+ for (let s of InspectorUtils.getAllStyleSheets(document)) {
+ if (SpecialPowers.unwrap(s) == sheet) {
+ adoptedCount++;
+ }
+ if (s.href === "resource://gre-resources/ua.css") {
+ foundUA = true;
+ }
+ }
+
+ ok(foundUA, "UA sheet should be returned with all the other sheets.");
+ is(adoptedCount, 1, "Adopted stylesheet should show up once");
+});
+</script>
diff --git a/layout/inspector/tests/test_is_element_themed.html b/layout/inspector/tests/test_is_element_themed.html
new file mode 100644
index 0000000000..e60ad1b3e0
--- /dev/null
+++ b/layout/inspector/tests/test_is_element_themed.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::IsElementThemed</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .not-themed-border:focus {
+ border: none;
+ }
+
+ .not-themed-background:focus {
+ background-color: red;
+ }
+ </style>
+</head>
+<body>
+<h1>Test InspectorUtils::isValidCSSColor</h1>
+
+<button id="themed-button">Themed Button</button>
+<input id="themed-input-number" type="number"/>
+<input id="themed-input-text" type="text"/>
+<textarea id="themed-textarea"></textarea>
+
+<button id="not-themed-button-(border)" class="not-themed-border">Not Themed Button</button>
+<input id="not-themed-input-number-(border)" class="not-themed-border" type="number"/>
+<input id="not-themed-input-text-(border)" class="not-themed-border" type="text"/>
+<textarea id="not-themed-textarea-(border)" class="not-themed-border"></textarea>
+
+<button id="not-themed-button-(background)" class="not-themed-background">Not Themed Button</button>
+<input id="not-themed-input-number-(background)" class="not-themed-background" type="number"/>
+<input id="not-themed-input-text-(background)" class="not-themed-background" type="text"/>
+<textarea id="not-themed-textarea-(background)" class="not-themed-background"></textarea>
+
+<script type="application/javascript">
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+ const tests = [{
+ id: "themed-button",
+ themed: true,
+ }, {
+ id: "themed-input-number",
+ themed: true,
+ }, {
+ id: "themed-input-text",
+ themed: true,
+ }, {
+ id: "themed-textarea",
+ themed: true,
+ }, {
+ id: "not-themed-button-(border)",
+ themed: false,
+ }, {
+ id: "not-themed-input-number-(border)",
+ themed: false,
+ }, {
+ id: "not-themed-input-text-(border)",
+ themed: false,
+ }, {
+ id: "not-themed-textarea-(border)",
+ themed: false,
+ }, {
+ id: "not-themed-button-(background)",
+ themed: false,
+ }, {
+ id: "not-themed-input-number-(background)",
+ themed: false,
+ }, {
+ id: "not-themed-input-text-(background)",
+ themed: false,
+ }, {
+ id: "not-themed-textarea-(background)",
+ themed: false,
+ }];
+
+ for (const { id, themed } of tests) {
+ ok(InspectorUtils.isElementThemed(document.getElementById(id)),
+ `#${id} is${themed ? " ": " not "}themed natively`);
+ }
+</script>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_is_valid_css_color.html b/layout/inspector/tests/test_is_valid_css_color.html
new file mode 100644
index 0000000000..d18a9ee384
--- /dev/null
+++ b/layout/inspector/tests/test_is_valid_css_color.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::isValidCSSColor</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ // Color names
+ // XXX getCSSValuesForProperty no longer returns the complete color
+ // keyword list, so skip it for now.
+ if (false) {
+ let colors = InspectorUtils.getCSSValuesForProperty("color");
+ let notColor = ["hsl", "hsla", "inherit", "initial", "rgb", "rgba",
+ "unset", "transparent", "currentColor"];
+ for (let color of colors) {
+ if (notColor.includes(color)) {
+ continue;
+ }
+ ok(InspectorUtils.isValidCSSColor(color), color + " is a valid color");
+ ok(!InspectorUtils.isValidCSSColor("xxx" + color), "xxx" + color + " is not a valid color");
+ }
+ }
+
+ // rgb(a)
+ for (let i = 0; i <= 265; i++) {
+ ok(InspectorUtils.isValidCSSColor("rgb(" + i + ",0,0)"), "rgb(" + i + ",0,0) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgb(0," + i + ",0)"), "rgb(0," + i + ",0) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgb(0,0," + i + ")"), "rgb(0,0," + i + ") is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgba(" + i + ",0,0,0.2)"), "rgba(" + i + ",0,0,0.2) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgba(0," + i + ",0,0.5)"), "rgba(0," + i + ",0,0.5) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgba(0,0," + i + ",0.7)"), "rgba(0,0," + i + ",0.7) is a valid color");
+
+ ok(!InspectorUtils.isValidCSSColor("rgbxxx(" + i + ",0,0)"), "rgbxxx(" + i + ",0,0) is not a valid color");
+ ok(!InspectorUtils.isValidCSSColor("rgbxxx(0," + i + ",0)"), "rgbxxx(0," + i + ",0) is not a valid color");
+ ok(!InspectorUtils.isValidCSSColor("rgbxxx(0,0," + i + ")"), "rgbxxx(0,0," + i + ") is not a valid color");
+ }
+
+ // rgb(a) (%)
+ for (let i = 0; i <= 110; i++) {
+ ok(InspectorUtils.isValidCSSColor("rgb(" + i + "%,0%,0%)"), "rgb(" + i + "%,0%,0%) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgb(0%," + i + "%,0%)"), "rgb(0%," + i + "%,0%) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgb(0%,0%," + i + "%)"), "rgb(0%,0%," + i + "%) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgba(" + i + "%,0%,0%,0.2)"), "rgba(" + i + "%,0%,0%,0.2) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgba(0%," + i + "%,0%,0.5)"), "rgba(0%," + i + "%,0%,0.5) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("rgba(0%,0%," + i + "%,0.7)"), "rgba(0%,0%," + i + "%,0.7) is a valid color");
+
+ ok(!InspectorUtils.isValidCSSColor("rgbaxxx(" + i + "%,0%,0%,0.2)"), "rgbaxxx(" + i + "%,0%,0%,0.2) is not a valid color");
+ ok(!InspectorUtils.isValidCSSColor("rgbaxxx(0%," + i + "%,0%,0.5)"), "rgbaxxx(0%," + i + "%,0%,0.5) is not a valid color");
+ ok(!InspectorUtils.isValidCSSColor("rgbaxxx(0%,0%," + i + "%,0.7)"), "rgbaxxx(0%,0%," + i + "%,0.7) is not a valid color");
+ }
+
+ // hsl(a)
+ for (let i = 0; i <= 370; i++) {
+ ok(InspectorUtils.isValidCSSColor("hsl(" + i + ",30%,10%)"), "rgb(" + i + ",30%,10%) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("hsla(" + i + ",60%,70%,0.2)"), "rgba(" + i + ",60%,70%,0.2) is a valid color");
+ }
+ for (let i = 0; i <= 110; i++) {
+ ok(InspectorUtils.isValidCSSColor("hsl(100," + i + "%,20%)"), "hsl(100," + i + "%,20%) is a valid color");
+ ok(InspectorUtils.isValidCSSColor("hsla(100,20%," + i + "%,0.6)"), "hsla(100,20%," + i + "%,0.6) is a valid color");
+ }
+
+ // hex
+ for (let i = 0; i <= 255; i++) {
+ let hex = (i).toString(16);
+ if (hex.length === 1) {
+ hex = 0 + hex;
+ }
+ ok(InspectorUtils.isValidCSSColor("#" + hex + "7777"), "#" + hex + "7777 is a valid color");
+ ok(InspectorUtils.isValidCSSColor("#77" + hex + "77"), "#77" + hex + "77 is a valid color");
+ ok(InspectorUtils.isValidCSSColor("#7777" + hex), "#7777" + hex + " is a valid color");
+ }
+ ok(!InspectorUtils.isValidCSSColor("#kkkkkk"), "#kkkkkk is not a valid color");
+
+ // short hex
+ for (let i = 0; i <= 16; i++) {
+ let hex = (i).toString(16);
+ ok(InspectorUtils.isValidCSSColor("#" + hex + hex + hex), "#" + hex + hex + hex + " is a valid color");
+ }
+ ok(!InspectorUtils.isValidCSSColor("#ggg"), "#ggg is not a valid color");
+
+ // named
+ ok(InspectorUtils.isValidCSSColor("red"), "red is a valid color");
+ ok(InspectorUtils.isValidCSSColor("transparent"), "transparent is a valid color");
+ ok(InspectorUtils.isValidCSSColor("currentColor"), "currentColor is a valid color");
+ </script>
+</head>
+<body>
+<h1>Test InspectorUtils::isValidCSSColor</h1>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_isinheritableproperty.html b/layout/inspector/tests/test_isinheritableproperty.html
new file mode 100644
index 0000000000..9f9340535c
--- /dev/null
+++ b/layout/inspector/tests/test_isinheritableproperty.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=699592
+-->
+<head>
+ <title>Test for InspectorUtils::isInheritedProperty</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @property --css-registered-inherits {
+ syntax: "*";
+ inherits: true;
+ }
+ @property --css-registered-no-inherits {
+ syntax: "*";
+ inherits: false;
+ }
+ </style>
+ <script>
+ CSS.registerProperty({
+ name: "--js-registered-inherits",
+ syntax: "*",
+ inherits: true,
+ });
+ CSS.registerProperty({
+ name: "--js-registered-no-inherits",
+ syntax: "*",
+ inherits: false,
+ });
+ </script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+function do_test() {
+ const isInherited = (name) =>
+ SpecialPowers.InspectorUtils.isInheritedProperty(document, name);
+
+ is(isInherited("font-size"), true, "font-size is inherited.");
+ is(isInherited("min-width"), false, "min-width is not inherited.");
+
+ is(isInherited("font"), true, "shorthand font property is inherited.");
+
+ is(isInherited("border"), false, "shorthand border property not inherited.");
+ is(isInherited("garbage"), false, "Unknown property isn't inherited.");
+
+ info("Checking isInheritedProperty result on custom properties");
+ is(isInherited("--unregistered-var"),true,
+ "Unregistered custom property is inherited."
+ );
+ is(
+ isInherited("--css-registered-inherits"),
+ true,
+ "Returns true for @property that inherits"
+ );
+ is(
+ isInherited("--css-registered-no-inherits"),
+ false,
+ "Returns false for @property that does not inherits"
+ );
+ is(
+ isInherited("--js-registered-inherits"),
+ true,
+ "Returns true for property registered in JS that inherits"
+ );
+ is(
+ isInherited("--js-registered-no-inherits"),
+ false,
+ "Returns false for property registered in JS that does not inherits"
+ );
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_parseStyleSheet.html b/layout/inspector/tests/test_parseStyleSheet.html
new file mode 100644
index 0000000000..98642ddfa4
--- /dev/null
+++ b/layout/inspector/tests/test_parseStyleSheet.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1195978
+-->
+ <head>
+ <title>Test for Bug 1195978</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ body {
+ color: red;
+ }
+ </style>
+ </head>
+ <body>
+ <script type="application/javascript">
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+var sheet = document.styleSheets[1];
+is(InspectorUtils.getRelativeRuleLine(sheet.cssRules[0]), 2,
+ "initial relative rule line");
+is(InspectorUtils.getRuleLine(sheet.cssRules[0]), 11,
+ "initial rule line");
+
+InspectorUtils.parseStyleSheet(sheet, "\nbody {\n color: blue;\n}\n");
+is(InspectorUtils.getRelativeRuleLine(sheet.cssRules[0]), 2,
+ "relative rule line after reparsing");
+is(InspectorUtils.getRuleLine(sheet.cssRules[0]), 11,
+ "relative rule line after reparsing");
+ </script>
+ </body>
+</html>
diff --git a/layout/inspector/tests/test_parseStyleSheetImport.html b/layout/inspector/tests/test_parseStyleSheetImport.html
new file mode 100644
index 0000000000..b46e0cf726
--- /dev/null
+++ b/layout/inspector/tests/test_parseStyleSheetImport.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1202095
+-->
+ <head>
+ <title>Test for Bug 1202095</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @import url('bug1202095.css');
+ @import url('bug1202095-2.css');
+ </style>
+ </head>
+ <body>
+ <script type="application/javascript">
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function do_test() {
+ var sheet = document.styleSheets[1];
+ var importRule = sheet.cssRules[0];
+ is(importRule.type, CSSRule.IMPORT_RULE,
+ "initial sheet has @import rule");
+
+ var importedSheet = importRule.styleSheet;
+ importedSheet.deleteRule(0);
+ is(importedSheet.cssRules.length, 0, "imported sheet now has no rules");
+
+ // "suffixed" refers to the "-2".
+ var suffixedSheet = sheet.cssRules[1].styleSheet;
+ InspectorUtils.parseStyleSheet(suffixedSheet, "");
+ is(suffixedSheet.cssRules.length, 0, "second imported sheet now has no rules");
+
+ // Re-parse the style sheet, preserving the imports.
+ InspectorUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');" +
+ "@import url('bug1202095-2.css');");
+ is(sheet.cssRules[0].type, CSSRule.IMPORT_RULE,
+ "re-parsed sheet has @import rule");
+ is(sheet.cssRules[0].styleSheet, importedSheet,
+ "imported sheet has not changed");
+ is(sheet.cssRules[1].styleSheet, suffixedSheet,
+ "second imported sheet has not changed");
+
+ // Re-parse the style sheet, preserving both imports, but changing
+ // the order.
+ InspectorUtils.parseStyleSheet(sheet, "@import url('bug1202095-2.css');" +
+ "@import url('bug1202095.css');");
+ is(sheet.cssRules[0].styleSheet, suffixedSheet,
+ "reordering preserved suffixed style sheet");
+ is(sheet.cssRules[1].styleSheet, importedSheet,
+ "reordering preserved unsuffixed style sheet");
+
+ // Re-parse the style sheet, removing the imports.
+ InspectorUtils.parseStyleSheet(sheet, "");
+ is(sheet.cssRules.length, 0, "style sheet now has no rules");
+
+ // Re-parse the style sheet, adding one import back. This should
+ // not allow reuse.
+ InspectorUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');");
+ is(sheet.cssRules[0].type, CSSRule.IMPORT_RULE,
+ "re-re-re-parsed sheet has @import rule");
+ isnot(sheet.cssRules[0].styleSheet, importedSheet,
+ "imported sheet has changed now");
+
+ // Re-parse the style sheet, importing the same URL twice.
+ // The style sheet should be reused once, but not two times.
+ importedSheet = sheet.cssRules[0].styleSheet;
+ InspectorUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');" +
+ "@import url('bug1202095.css');");
+ is(sheet.cssRules[0].styleSheet, importedSheet,
+ "first imported sheet is reused");
+ isnot(sheet.cssRules[1].styleSheet, importedSheet,
+ "second imported sheet is reused");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+ </script>
+ </body>
+</html>
diff --git a/layout/inspector/tests/test_parseStyleSheet_nested.html b/layout/inspector/tests/test_parseStyleSheet_nested.html
new file mode 100644
index 0000000000..15dbd71783
--- /dev/null
+++ b/layout/inspector/tests/test_parseStyleSheet_nested.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test InspectorUtils.parseStyleSheet with nested rules</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style>
+ h1 {
+ .mySpan {
+ background: gold;
+ &.mySpan {
+ color: red;
+ }
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Hello<span class="mySpan">world</span>
+ <script>
+ add_task(function() {
+ info("Flush layout");
+ // This is important to reproduce the original issue
+ document.documentElement.getBoundingClientRect();
+
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+ const sheet = document.styleSheets[0];
+ const spanEl = document.querySelector(".mySpan");
+
+ is(
+ sheet.cssRules[0].cssRules[0].cssRules[0].cssText,
+ `&.mySpan { color: red; }`,
+ "Nested rule has expected initial text"
+ );
+
+ is(
+ InspectorUtils.getCSSStyleRules(spanEl).length,
+ 2,
+ "getCSSStyleRules returned 2 rules for .mySpan"
+ );
+
+ info("Modify stylesheet using InspectorUtils.parseStyleSheet");
+ InspectorUtils.parseStyleSheet(
+ sheet,
+ `h1 {
+ .mySpan {
+ background: gold;
+ &.mySpan {
+ color: black;
+ }
+ }
+ }`
+ );
+
+ is(
+ sheet.cssRules[0].cssRules[0].cssRules[0].cssText,
+ `&.mySpan { color: black; }`,
+ "Nested rule has expected text after updating the stylesheet"
+ );
+
+ info("Flush layout");
+ // This is important to reproduce the original issue
+ document.documentElement.getBoundingClientRect();
+
+ is(
+ getComputedStyle(spanEl).color,
+ "rgb(0, 0, 0)",
+ "the color of the span element was properly updated"
+ );
+ const rules = InspectorUtils.getCSSStyleRules(spanEl);
+ is(
+ rules.length,
+ 2,
+ "getCSSStyleRules still returned 2 rules for .mySpan after stylesheet was updated"
+ );
+ is(rules[1].style.color, "black", "rule was properly updated");
+ });
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/inspector/tests/test_rgba_to_color_name.html b/layout/inspector/tests/test_rgba_to_color_name.html
new file mode 100644
index 0000000000..50764e4e56
--- /dev/null
+++ b/layout/inspector/tests/test_rgba_to_color_name.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test InspectorUtils::RgbToColorName</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ testColor({r:255, g:0, b:0, a:1}, "red");
+ testColor({r:0, g:0, b:255, a:1}, "blue");
+ testColor({r:0, g:0, b:255, a:0}, "blue");
+ testColor(InspectorUtils.colorToRGBA("tomato"), "tomato");
+
+ // No matching named color should return an empty string
+ testColor({r:1, g:1, b:1, a:1}, "");
+
+ function testColor({r, g, b, a}, expectedColor) {
+ const colorName = InspectorUtils.rgbToColorName(r, g, b, a);
+ is(colorName, expectedColor, `Got expected result for ${r} ${b} ${b} / ${a}`);
+ }
+ </script>
+</head>
+<body>
+<h1>Test InspectorUtils::RgbToColorName</h1>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_selectormatcheselement.html b/layout/inspector/tests/test_selectormatcheselement.html
new file mode 100644
index 0000000000..b996b241c8
--- /dev/null
+++ b/layout/inspector/tests/test_selectormatcheselement.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1037519
+-->
+<head>
+ <title>Test for nsIDOMUtils::selectorMatchesElement</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #foo,
+ #bar,
+ #foo::before {
+ color: red;
+ }
+
+ #foo {
+ div& {
+ outline: 1px solid gold;
+ }
+ }
+
+ #foo::before,
+ #bar::before {
+ content: 'foo-before';
+ color: green;
+ }
+ #foo::after,
+ #bar::after {
+ content: 'foo-after';
+ color: blue;
+ }
+ #foo::first-line,
+ #bar::first-line {
+ text-decoration: underline;
+ }
+ #foo::first-letter,
+ #bar::first-letter {
+ font-variant: small-caps;
+ }
+ </style>
+</head>
+<body>
+<div id="foo">foo content</div>
+<pre id="test">
+<script type="application/javascript">
+
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function do_test() {
+ var element = document.querySelector("#foo");
+
+ var elementRules = InspectorUtils.getCSSStyleRules(element);
+
+ var multiSelectorRule = elementRules[2];
+ is(multiSelectorRule.selectorText, `#foo, #bar, #foo::before`, "Got expected multi-selector rule");
+ is (multiSelectorRule.selectorMatchesElement(0, element), true,
+ "Matches #foo");
+ is (multiSelectorRule.selectorMatchesElement(1, element), false,
+ "Doesn't match #bar");
+ is (multiSelectorRule.selectorMatchesElement(0, element, ":bogus"), false,
+ "Doesn't match #foo with a bogus pseudo");
+ is (multiSelectorRule.selectorMatchesElement(2, element, ":bogus"), false,
+ "Doesn't match #foo::before with bogus pseudo");
+ is (multiSelectorRule.selectorMatchesElement(0, element, ":after"), false,
+ "Does match #foo::before with the :after pseudo");
+
+ var nestedRule = elementRules[4];
+ is(nestedRule.selectorText, `div&`, "Got expected nested rule");
+ is (nestedRule.selectorMatchesElement(0, element), true, "Matches div&");
+
+ checkPseudo(":before");
+ checkPseudo(":after");
+ checkPseudo(":first-letter");
+ checkPseudo(":first-line");
+
+ SimpleTest.finish();
+
+ function checkPseudo(pseudo) {
+ var rules = InspectorUtils.getCSSStyleRules(element, pseudo);
+ var rule = rules[rules.length - 1];
+
+ is (rule.selectorMatchesElement(0, element), false,
+ "Doesn't match without " + pseudo);
+ is (rule.selectorMatchesElement(1, element), false,
+ "Doesn't match without " + pseudo);
+
+ is (rule.selectorMatchesElement(0, element, pseudo), true,
+ "Matches on #foo" + pseudo);
+ is (rule.selectorMatchesElement(1, element, pseudo), false,
+ "Doesn't match on #bar" + pseudo);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_supports.html b/layout/inspector/tests/test_supports.html
new file mode 100644
index 0000000000..9a698088dd
--- /dev/null
+++ b/layout/inspector/tests/test_supports.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>Test for InspectorUtils.supports</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ok(!CSS.supports("-moz-window-transform: unset"), "-moz-window-transform is only available to chrome and UA sheets");
+for (let chrome of [true, false]) {
+ is(InspectorUtils.supports("-moz-window-transform: unset", { chrome }), chrome, "InspectorUtils.supports should properly report to support chrome-only properties");
+}
+
+ok(!CSS.supports("-moz-top-layer: top"), "-moz-top-layer is only available to UA sheets");
+ok(!CSS.supports("selector(:-moz-inert)"), "-moz-inert is an UA-only pseudo-class");
+for (let userAgent of [true, false]) {
+ is(InspectorUtils.supports("-moz-top-layer: top", { userAgent }), userAgent, "InspectorUtils.supports should properly report to support UA properties");
+ is(InspectorUtils.supports("selector(:-moz-inert)", { userAgent }), userAgent, "InspectorUtils.supports should properly report to support UA-only selectors");
+}
+
+ok(!CSS.supports("width: 100"), "width shouldn't allow unitless lengths in non-quirks, and in CSS.supports");
+for (let quirks of [true, false]) {
+ is(InspectorUtils.supports("width: 100", { quirks }), quirks, "InspectorUtils.supports should allow quirks if told to do so");
+}
+</script>