diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/inspector | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/inspector')
62 files changed, 7494 insertions, 0 deletions
diff --git a/layout/inspector/inCSSValueSearch.cpp b/layout/inspector/inCSSValueSearch.cpp new file mode 100644 index 000000000..ecde09993 --- /dev/null +++ b/layout/inspector/inCSSValueSearch.cpp @@ -0,0 +1,407 @@ +/* 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 "inCSSValueSearch.h" + +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/dom/StyleSheetList.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsReadableUtils.h" +#include "nsIDOMDocument.h" +#include "nsIDOMStyleSheetList.h" +#include "nsIDOMCSSStyleSheet.h" +#include "nsIDOMCSSRuleList.h" +#include "nsIDOMCSSStyleRule.h" +#include "nsIDOMCSSStyleDeclaration.h" +#include "nsIDOMCSSImportRule.h" +#include "nsIDOMCSSMediaRule.h" +#include "nsIDOMCSSSupportsRule.h" +#include "nsIURI.h" +#include "nsIDocument.h" +#include "nsNetUtil.h" + +using namespace mozilla; + +/////////////////////////////////////////////////////////////////////////////// +inCSSValueSearch::inCSSValueSearch() + : mResults(nullptr), + mProperties(nullptr), + mResultCount(0), + mPropertyCount(0), + mIsActive(false), + mHoldResults(true), + mReturnRelativeURLs(true), + mNormalizeChromeURLs(false) +{ + nsCSSProps::AddRefTable(); + mProperties = new nsCSSPropertyID[100]; +} + +inCSSValueSearch::~inCSSValueSearch() +{ + delete[] mProperties; + delete mResults; + nsCSSProps::ReleaseTable(); +} + +NS_IMPL_ISUPPORTS(inCSSValueSearch, inISearchProcess, inICSSValueSearch) + +/////////////////////////////////////////////////////////////////////////////// +// inISearchProcess + +NS_IMETHODIMP +inCSSValueSearch::GetIsActive(bool *aIsActive) +{ + *aIsActive = mIsActive; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetResultCount(int32_t *aResultCount) +{ + *aResultCount = mResultCount; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetHoldResults(bool *aHoldResults) +{ + *aHoldResults = mHoldResults; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SetHoldResults(bool aHoldResults) +{ + mHoldResults = aHoldResults; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SearchSync() +{ + InitSearch(); + + if (!mDocument) { + return NS_OK; + } + + nsCOMPtr<nsIDocument> document = do_QueryInterface(mDocument); + MOZ_ASSERT(document); + + nsCOMPtr<nsIURI> baseURI = document->GetBaseURI(); + + RefPtr<dom::StyleSheetList> sheets = document->StyleSheets(); + MOZ_ASSERT(sheets); + + uint32_t length = sheets->Length(); + for (uint32_t i = 0; i < length; ++i) { + RefPtr<StyleSheet> sheet = sheets->Item(i); + SearchStyleSheet(sheet, baseURI); + } + + // XXX would be nice to search inline style as well. + + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SearchAsync(inISearchObserver *aObserver) +{ + InitSearch(); + mObserver = aObserver; + + return NS_OK; +} + + +NS_IMETHODIMP +inCSSValueSearch::SearchStop() +{ + KillSearch(inISearchObserver::IN_INTERRUPTED); + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SearchStep(bool* _retval) +{ + + return NS_OK; +} + + +NS_IMETHODIMP +inCSSValueSearch::GetStringResultAt(int32_t aIndex, nsAString& _retval) +{ + if (mHoldResults) { + nsAutoString* result = mResults->ElementAt(aIndex); + _retval = *result; + } else if (aIndex == mResultCount-1) { + _retval = mLastResult; + } else { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetIntResultAt(int32_t aIndex, int32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +inCSSValueSearch::GetUIntResultAt(int32_t aIndex, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/////////////////////////////////////////////////////////////////////////////// +// inICSSValueSearch + +NS_IMETHODIMP +inCSSValueSearch::GetDocument(nsIDOMDocument** aDocument) +{ + *aDocument = mDocument; + NS_IF_ADDREF(*aDocument); + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SetDocument(nsIDOMDocument* aDocument) +{ + mDocument = aDocument; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetBaseURL(char16_t** aBaseURL) +{ + if (!(*aBaseURL = ToNewUnicode(mBaseURL))) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SetBaseURL(const char16_t* aBaseURL) +{ + mBaseURL.Assign(aBaseURL); + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetReturnRelativeURLs(bool* aReturnRelativeURLs) +{ + *aReturnRelativeURLs = mReturnRelativeURLs; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SetReturnRelativeURLs(bool aReturnRelativeURLs) +{ + mReturnRelativeURLs = aReturnRelativeURLs; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetNormalizeChromeURLs(bool *aNormalizeChromeURLs) +{ + *aNormalizeChromeURLs = mNormalizeChromeURLs; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SetNormalizeChromeURLs(bool aNormalizeChromeURLs) +{ + mNormalizeChromeURLs = aNormalizeChromeURLs; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::AddPropertyCriteria(const char16_t *aPropName) +{ + nsCSSPropertyID prop = + nsCSSProps::LookupProperty(nsDependentString(aPropName), + CSSEnabledState::eIgnoreEnabledState); + mProperties[mPropertyCount] = prop; + mPropertyCount++; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::GetTextCriteria(char16_t** aTextCriteria) +{ + if (!(*aTextCriteria = ToNewUnicode(mTextCriteria))) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +NS_IMETHODIMP +inCSSValueSearch::SetTextCriteria(const char16_t* aTextCriteria) +{ + mTextCriteria.Assign(aTextCriteria); + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// inCSSValueSearch + +nsresult +inCSSValueSearch::InitSearch() +{ + if (mHoldResults) { + mResults = new nsTArray<nsAutoString *>(); + } + + mResultCount = 0; + + return NS_OK; +} + +nsresult +inCSSValueSearch::KillSearch(int16_t aResult) +{ + mIsActive = true; + mObserver->OnSearchEnd(this, aResult); + + return NS_OK; +} + +nsresult +inCSSValueSearch::SearchStyleSheet(nsIDOMCSSStyleSheet* aStyleSheet, nsIURI* aBaseURL) +{ + nsCOMPtr<nsIURI> baseURL; + nsAutoString href; + aStyleSheet->GetHref(href); + if (href.IsEmpty()) + baseURL = aBaseURL; + else + NS_NewURI(getter_AddRefs(baseURL), href, nullptr, aBaseURL); + + nsCOMPtr<nsIDOMCSSRuleList> rules; + nsresult rv = aStyleSheet->GetCssRules(getter_AddRefs(rules)); + NS_ENSURE_SUCCESS(rv, rv); + + return SearchRuleList(rules, baseURL); +} + +nsresult +inCSSValueSearch::SearchRuleList(nsIDOMCSSRuleList* aRuleList, nsIURI* aBaseURL) +{ + uint32_t length; + aRuleList->GetLength(&length); + for (uint32_t i = 0; i < length; ++i) { + nsCOMPtr<nsIDOMCSSRule> rule; + aRuleList->Item(i, getter_AddRefs(rule)); + uint16_t type; + rule->GetType(&type); + switch (type) { + case nsIDOMCSSRule::STYLE_RULE: { + nsCOMPtr<nsIDOMCSSStyleRule> styleRule = do_QueryInterface(rule); + SearchStyleRule(styleRule, aBaseURL); + } break; + case nsIDOMCSSRule::IMPORT_RULE: { + nsCOMPtr<nsIDOMCSSImportRule> importRule = do_QueryInterface(rule); + nsCOMPtr<nsIDOMCSSStyleSheet> childSheet; + importRule->GetStyleSheet(getter_AddRefs(childSheet)); + if (childSheet) + SearchStyleSheet(childSheet, aBaseURL); + } break; + case nsIDOMCSSRule::MEDIA_RULE: { + nsCOMPtr<nsIDOMCSSMediaRule> mediaRule = do_QueryInterface(rule); + nsCOMPtr<nsIDOMCSSRuleList> childRules; + mediaRule->GetCssRules(getter_AddRefs(childRules)); + SearchRuleList(childRules, aBaseURL); + } break; + case nsIDOMCSSRule::SUPPORTS_RULE: { + nsCOMPtr<nsIDOMCSSSupportsRule> supportsRule = do_QueryInterface(rule); + nsCOMPtr<nsIDOMCSSRuleList> childRules; + supportsRule->GetCssRules(getter_AddRefs(childRules)); + SearchRuleList(childRules, aBaseURL); + } break; + default: + // XXX handle nsIDOMCSSRule::PAGE_RULE if we ever support it + break; + } + } + return NS_OK; +} + +nsresult +inCSSValueSearch::SearchStyleRule(nsIDOMCSSStyleRule* aStyleRule, nsIURI* aBaseURL) +{ + nsCOMPtr<nsIDOMCSSStyleDeclaration> decl; + nsresult rv = aStyleRule->GetStyle(getter_AddRefs(decl)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t length; + decl->GetLength(&length); + nsAutoString property, value; + for (uint32_t i = 0; i < length; ++i) { + decl->Item(i, property); + // XXX This probably ought to use GetPropertyCSSValue if it were + // implemented. + decl->GetPropertyValue(property, value); + SearchStyleValue(value, aBaseURL); + } + return NS_OK; +} + +nsresult +inCSSValueSearch::SearchStyleValue(const nsAFlatString& aValue, nsIURI* aBaseURL) +{ + if (StringBeginsWith(aValue, NS_LITERAL_STRING("url(")) && + StringEndsWith(aValue, NS_LITERAL_STRING(")"))) { + const nsASingleFragmentString &url = + Substring(aValue, 4, aValue.Length() - 5); + // XXXldb Need to do more with |mReturnRelativeURLs|, perhaps? + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, aBaseURL); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString spec; + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString *result = new NS_ConvertUTF8toUTF16(spec); + if (mReturnRelativeURLs) + EqualizeURL(result); + mResults->AppendElement(result); + ++mResultCount; + } + + return NS_OK; +} + +nsresult +inCSSValueSearch::EqualizeURL(nsAutoString* aURL) +{ + if (mNormalizeChromeURLs) { + if (aURL->Find("chrome://", false, 0, 1) >= 0) { + uint32_t len = aURL->Length(); + char16_t* result = new char16_t[len-8]; + const char16_t* src = aURL->get(); + uint32_t i = 9; + uint32_t milestone = 0; + uint32_t s = 0; + while (i < len) { + if (src[i] == '/') { + milestone += 1; + } + if (milestone != 1) { + result[i-9-s] = src[i]; + } else { + s++; + } + i++; + } + result[i-9-s] = 0; + + aURL->Assign(result); + delete [] result; + } + } else { + } + + return NS_OK; +} diff --git a/layout/inspector/inCSSValueSearch.h b/layout/inspector/inCSSValueSearch.h new file mode 100644 index 000000000..3741fd331 --- /dev/null +++ b/layout/inspector/inCSSValueSearch.h @@ -0,0 +1,60 @@ +/* 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 __inCSSValueSearch_h__ +#define __inCSSValueSearch_h__ + +#include "inICSSValueSearch.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIDOMDocument.h" +#include "inISearchObserver.h" +#include "nsTArray.h" +#include "nsCSSProps.h" + +class nsIDOMCSSStyleSheet; +class nsIDOMCSSRuleList; +class nsIDOMCSSStyleRule; +class nsIURI; + +class inCSSValueSearch final : public inICSSValueSearch +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_INISEARCHPROCESS + NS_DECL_INICSSVALUESEARCH + + inCSSValueSearch(); + +protected: + virtual ~inCSSValueSearch(); + nsCOMPtr<inISearchObserver> mObserver; + nsCOMPtr<nsIDOMDocument> mDocument; + nsTArray<nsAutoString *>* mResults; + nsCSSPropertyID* mProperties; + nsString mLastResult; + nsString mBaseURL; + nsString mTextCriteria; + int32_t mResultCount; + uint32_t mPropertyCount; + bool mIsActive; + bool mHoldResults; + bool mReturnRelativeURLs; + bool mNormalizeChromeURLs; + + nsresult InitSearch(); + nsresult KillSearch(int16_t aResult); + nsresult SearchStyleSheet(nsIDOMCSSStyleSheet* aStyleSheet, nsIURI* aBaseURI); + nsresult SearchRuleList(nsIDOMCSSRuleList* aRuleList, nsIURI* aBaseURI); + nsresult SearchStyleRule(nsIDOMCSSStyleRule* aStyleRule, nsIURI* aBaseURI); + nsresult SearchStyleValue(const nsAFlatString& aValue, nsIURI* aBaseURI); + nsresult EqualizeURL(nsAutoString* aURL); +}; + +// {4D977F60-FBE7-4583-8CB7-F5ED882293EF} +#define IN_CSSVALUESEARCH_CID \ +{ 0x4d977f60, 0xfbe7, 0x4583, { 0x8c, 0xb7, 0xf5, 0xed, 0x88, 0x22, 0x93, 0xef } } + +#endif // __inCSSValueSearch_h__ diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp new file mode 100644 index 000000000..9f1dcaad3 --- /dev/null +++ b/layout/inspector/inDOMUtils.cpp @@ -0,0 +1,1351 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/EventStates.h" + +#include "inDOMUtils.h" +#include "inLayoutUtils.h" + +#include "nsArray.h" +#include "nsAutoPtr.h" +#include "nsIServiceManager.h" +#include "nsString.h" +#include "nsIStyleSheetLinkingElement.h" +#include "nsIContentInlines.h" +#include "nsIDOMElement.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" +#include "nsIDOMDocument.h" +#include "nsIDOMCharacterData.h" +#include "nsRuleNode.h" +#include "nsIStyleRule.h" +#include "mozilla/css/StyleRule.h" +#include "nsICSSStyleRuleDOMWrapper.h" +#include "nsIDOMWindow.h" +#include "nsXBLBinding.h" +#include "nsXBLPrototypeBinding.h" +#include "nsIMutableArray.h" +#include "nsBindingManager.h" +#include "ChildIterator.h" +#include "nsComputedDOMStyle.h" +#include "mozilla/EventStateManager.h" +#include "nsIAtom.h" +#include "nsRange.h" +#include "nsContentList.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/dom/Element.h" +#include "nsRuleWalker.h" +#include "nsRuleProcessorData.h" +#include "nsCSSPseudoClasses.h" +#include "nsCSSRuleProcessor.h" +#include "mozilla/dom/CSSLexer.h" +#include "mozilla/dom/InspectorUtilsBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsCSSParser.h" +#include "nsCSSProps.h" +#include "nsCSSValue.h" +#include "nsColor.h" +#include "nsStyleSet.h" +#include "nsStyleUtil.h" +#include "nsQueryObject.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::dom; + +/////////////////////////////////////////////////////////////////////////////// + +inDOMUtils::inDOMUtils() +{ +} + +inDOMUtils::~inDOMUtils() +{ +} + +NS_IMPL_ISUPPORTS(inDOMUtils, inIDOMUtils) + +/////////////////////////////////////////////////////////////////////////////// +// inIDOMUtils + +NS_IMETHODIMP +inDOMUtils::GetAllStyleSheets(nsIDOMDocument *aDocument, uint32_t *aLength, + nsISupports ***aSheets) +{ + NS_ENSURE_ARG_POINTER(aDocument); + + nsTArray<RefPtr<CSSStyleSheet>> sheets; + + nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument); + MOZ_ASSERT(document); + + // Get the agent, then user and finally xbl sheets in the style set. + nsIPresShell* presShell = document->GetShell(); + + if (presShell && presShell->StyleSet()->IsServo()) { + // XXXheycam ServoStyleSets don't have the ability to expose their + // sheets in a script-accessible way yet. + NS_ERROR("stylo: ServoStyleSets cannot expose their sheets to script yet"); + return NS_ERROR_FAILURE; + } + + if (presShell) { + nsStyleSet* styleSet = presShell->StyleSet()->AsGecko(); + SheetType sheetType = SheetType::Agent; + for (int32_t i = 0; i < styleSet->SheetCount(sheetType); i++) { + sheets.AppendElement(styleSet->StyleSheetAt(sheetType, i)); + } + sheetType = SheetType::User; + for (int32_t i = 0; i < styleSet->SheetCount(sheetType); i++) { + sheets.AppendElement(styleSet->StyleSheetAt(sheetType, i)); + } + AutoTArray<CSSStyleSheet*, 32> xblSheetArray; + styleSet->AppendAllXBLStyleSheets(xblSheetArray); + + // The XBL stylesheet array will quite often be full of duplicates. Cope: + nsTHashtable<nsPtrHashKey<CSSStyleSheet>> sheetSet; + for (CSSStyleSheet* sheet : xblSheetArray) { + if (!sheetSet.Contains(sheet)) { + sheetSet.PutEntry(sheet); + sheets.AppendElement(sheet); + } + } + } + + // Get the document sheets. + for (int32_t i = 0; i < document->GetNumberOfStyleSheets(); i++) { + // XXXheycam ServoStyleSets don't have the ability to expose their + // sheets in a script-accessible way yet. + sheets.AppendElement(document->GetStyleSheetAt(i)->AsGecko()); + } + + nsISupports** ret = static_cast<nsISupports**>(moz_xmalloc(sheets.Length() * + sizeof(nsISupports*))); + + for (size_t i = 0; i < sheets.Length(); i++) { + NS_ADDREF(ret[i] = NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, sheets[i])); + } + + *aLength = sheets.Length(); + *aSheets = ret; + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::IsIgnorableWhitespace(nsIDOMCharacterData *aDataNode, + bool *aReturn) +{ + NS_PRECONDITION(aReturn, "Must have an out parameter"); + + NS_ENSURE_ARG_POINTER(aDataNode); + + *aReturn = false; + + nsCOMPtr<nsIContent> content = do_QueryInterface(aDataNode); + NS_ASSERTION(content, "Does not implement nsIContent!"); + + if (!content->TextIsOnlyWhitespace()) { + return NS_OK; + } + + // Okay. We have only white space. Let's check the white-space + // property now and make sure that this isn't preformatted text... + nsIFrame* frame = content->GetPrimaryFrame(); + if (frame) { + const nsStyleText* text = frame->StyleText(); + *aReturn = !text->WhiteSpaceIsSignificant(); + } + else { + // empty inter-tag text node without frame, e.g., in between <table>\n<tr> + *aReturn = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetParentForNode(nsIDOMNode* aNode, + bool aShowingAnonymousContent, + nsIDOMNode** aParent) +{ + NS_ENSURE_ARG_POINTER(aNode); + + // First do the special cases -- document nodes and anonymous content + nsCOMPtr<nsIDocument> doc(do_QueryInterface(aNode)); + nsCOMPtr<nsIDOMNode> parent; + + if (doc) { + parent = inLayoutUtils::GetContainerFor(*doc); + } else if (aShowingAnonymousContent) { + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + if (content) { + nsIContent* bparent = content->GetFlattenedTreeParent(); + parent = do_QueryInterface(bparent); + } + } + + if (!parent) { + // Ok, just get the normal DOM parent node + aNode->GetParentNode(getter_AddRefs(parent)); + } + + NS_IF_ADDREF(*aParent = parent); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetChildrenForNode(nsIDOMNode* aNode, + bool aShowingAnonymousContent, + nsIDOMNodeList** aChildren) +{ + NS_ENSURE_ARG_POINTER(aNode); + NS_PRECONDITION(aChildren, "Must have an out parameter"); + + nsCOMPtr<nsIDOMNodeList> kids; + + if (aShowingAnonymousContent) { + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + if (content) { + kids = content->GetChildren(nsIContent::eAllChildren); + } + } + + if (!kids) { + aNode->GetChildNodes(getter_AddRefs(kids)); + } + + kids.forget(aChildren); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetCSSStyleRules(nsIDOMElement *aElement, + const nsAString& aPseudo, + nsIArrayExtensions **_retval) +{ + NS_ENSURE_ARG_POINTER(aElement); + + *_retval = nullptr; + + nsCOMPtr<nsIAtom> pseudoElt; + if (!aPseudo.IsEmpty()) { + pseudoElt = NS_Atomize(aPseudo); + } + + nsRuleNode* ruleNode = nullptr; + nsCOMPtr<Element> element = do_QueryInterface(aElement); + NS_ENSURE_STATE(element); + RefPtr<nsStyleContext> styleContext; + GetRuleNodeForElement(element, pseudoElt, getter_AddRefs(styleContext), &ruleNode); + if (!ruleNode) { + // 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 NS_OK; + } + + AutoTArray<nsRuleNode*, 16> ruleNodes; + while (!ruleNode->IsRoot()) { + ruleNodes.AppendElement(ruleNode); + ruleNode = ruleNode->GetParent(); + } + + nsCOMPtr<nsIMutableArray> rules = nsArray::Create(); + for (nsRuleNode* ruleNode : Reversed(ruleNodes)) { + RefPtr<Declaration> decl = do_QueryObject(ruleNode->GetRule()); + if (decl) { + RefPtr<mozilla::css::StyleRule> styleRule = + do_QueryObject(decl->GetOwningRule()); + if (styleRule) { + nsCOMPtr<nsIDOMCSSRule> domRule = styleRule->GetDOMRule(); + if (domRule) { + rules->AppendElement(domRule, /*weak =*/ false); + } + } + } + } + + rules.forget(_retval); + + return NS_OK; +} + +static already_AddRefed<StyleRule> +GetRuleFromDOMRule(nsIDOMCSSStyleRule *aRule, ErrorResult& rv) +{ + nsCOMPtr<nsICSSStyleRuleDOMWrapper> rule = do_QueryInterface(aRule); + if (!rule) { + rv.Throw(NS_ERROR_INVALID_POINTER); + return nullptr; + } + + RefPtr<StyleRule> cssrule; + rv = rule->GetCSSStyleRule(getter_AddRefs(cssrule)); + if (rv.Failed()) { + return nullptr; + } + + if (!cssrule) { + rv.Throw(NS_ERROR_FAILURE); + } + return cssrule.forget(); +} + +NS_IMETHODIMP +inDOMUtils::GetRuleLine(nsIDOMCSSRule* aRule, uint32_t* _retval) +{ + NS_ENSURE_ARG_POINTER(aRule); + + Rule* rule = aRule->GetCSSRule(); + if (!rule) { + return NS_ERROR_FAILURE; + } + + *_retval = rule->GetLineNumber(); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetRuleColumn(nsIDOMCSSRule* aRule, uint32_t* _retval) +{ + NS_ENSURE_ARG_POINTER(aRule); + + Rule* rule = aRule->GetCSSRule(); + if (!rule) { + return NS_ERROR_FAILURE; + } + + *_retval = rule->GetColumnNumber(); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetRelativeRuleLine(nsIDOMCSSRule* aRule, uint32_t* _retval) +{ + NS_ENSURE_ARG_POINTER(aRule); + + Rule* rule = aRule->GetCSSRule(); + if (!rule) { + return NS_ERROR_FAILURE; + } + + uint32_t lineNumber = rule->GetLineNumber(); + CSSStyleSheet* sheet = rule->GetStyleSheet(); + if (sheet && lineNumber != 0) { + nsINode* owningNode = sheet->GetOwnerNode(); + if (owningNode) { + nsCOMPtr<nsIStyleSheetLinkingElement> link = + do_QueryInterface(owningNode); + if (link) { + lineNumber -= link->GetLineNumber() - 1; + } + } + } + + *_retval = lineNumber; + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetCSSLexer(const nsAString& aText, JSContext* aCx, + JS::MutableHandleValue aResult) +{ + MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); + JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx)); + nsAutoPtr<CSSLexer> lexer(new CSSLexer(aText)); + if (!WrapNewBindingNonWrapperCachedObject(aCx, scope, lexer, aResult)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetSelectorCount(nsIDOMCSSStyleRule* aRule, uint32_t *aCount) +{ + ErrorResult rv; + RefPtr<StyleRule> rule = GetRuleFromDOMRule(aRule, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + uint32_t count = 0; + for (nsCSSSelectorList* sel = rule->Selector(); sel; sel = sel->mNext) { + ++count; + } + *aCount = count; + return NS_OK; +} + +static nsCSSSelectorList* +GetSelectorAtIndex(nsIDOMCSSStyleRule* aRule, uint32_t aIndex, ErrorResult& rv) +{ + RefPtr<StyleRule> rule = GetRuleFromDOMRule(aRule, rv); + if (rv.Failed()) { + return nullptr; + } + + for (nsCSSSelectorList* sel = rule->Selector(); sel; + sel = sel->mNext, --aIndex) { + if (aIndex == 0) { + return sel; + } + } + + // Ran out of selectors + rv.Throw(NS_ERROR_INVALID_ARG); + return nullptr; +} + +NS_IMETHODIMP +inDOMUtils::GetSelectorText(nsIDOMCSSStyleRule* aRule, + uint32_t aSelectorIndex, + nsAString& aText) +{ + ErrorResult rv; + nsCSSSelectorList* sel = GetSelectorAtIndex(aRule, aSelectorIndex, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + RefPtr<StyleRule> rule = GetRuleFromDOMRule(aRule, rv); + MOZ_ASSERT(!rv.Failed(), "How could we get a selector but not a rule?"); + + sel->mSelectors->ToString(aText, rule->GetStyleSheet(), false); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetSpecificity(nsIDOMCSSStyleRule* aRule, + uint32_t aSelectorIndex, + uint64_t* aSpecificity) +{ + ErrorResult rv; + nsCSSSelectorList* sel = GetSelectorAtIndex(aRule, aSelectorIndex, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + *aSpecificity = sel->mWeight; + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::SelectorMatchesElement(nsIDOMElement* aElement, + nsIDOMCSSStyleRule* aRule, + uint32_t aSelectorIndex, + const nsAString& aPseudo, + bool* aMatches) +{ + nsCOMPtr<Element> element = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(element); + + ErrorResult rv; + nsCSSSelectorList* tail = GetSelectorAtIndex(aRule, aSelectorIndex, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + // We want just the one list item, not the whole list tail + nsAutoPtr<nsCSSSelectorList> sel(tail->Clone(false)); + + // Do not attempt to match if a pseudo element is requested and this is not + // a pseudo element selector, or vice versa. + if (aPseudo.IsEmpty() == sel->mSelectors->IsPseudoElement()) { + *aMatches = false; + return NS_OK; + } + + if (!aPseudo.IsEmpty()) { + // We need to make sure that the requested pseudo element type + // matches the selector pseudo element type before proceeding. + nsCOMPtr<nsIAtom> pseudoElt = NS_Atomize(aPseudo); + if (sel->mSelectors->PseudoType() != nsCSSPseudoElements:: + GetPseudoType(pseudoElt, CSSEnabledState::eIgnoreEnabledState)) { + *aMatches = false; + return NS_OK; + } + + // We have a matching pseudo element, now remove it so we can compare + // directly against |element| when proceeding into SelectorListMatches. + // It's OK to do this - we just cloned sel and nothing else is using it. + sel->RemoveRightmostSelector(); + } + + element->OwnerDoc()->FlushPendingLinkUpdates(); + // XXXbz what exactly should we do with visited state here? + TreeMatchContext matchingContext(false, + nsRuleWalker::eRelevantLinkUnvisited, + element->OwnerDoc(), + TreeMatchContext::eNeverMatchVisited); + *aMatches = nsCSSRuleProcessor::SelectorListMatches(element, matchingContext, + sel); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::IsInheritedProperty(const nsAString &aPropertyName, bool *_retval) +{ + nsCSSPropertyID prop = nsCSSProps:: + LookupProperty(aPropertyName, CSSEnabledState::eIgnoreEnabledState); + if (prop == eCSSProperty_UNKNOWN) { + *_retval = false; + return NS_OK; + } + + if (prop == eCSSPropertyExtra_variable) { + *_retval = true; + return NS_OK; + } + + if (nsCSSProps::IsShorthand(prop)) { + prop = nsCSSProps::SubpropertyEntryFor(prop)[0]; + } + + nsStyleStructID sid = nsCSSProps::kSIDTable[prop]; + *_retval = !nsCachedStyleData::IsReset(sid); + return NS_OK; +} + +extern const char* const kCSSRawProperties[]; + +NS_IMETHODIMP +inDOMUtils::GetCSSPropertyNames(uint32_t aFlags, uint32_t* aCount, + char16_t*** aProps) +{ + // maxCount is the largest number of properties we could have; our actual + // number might be smaller because properties might be disabled. + uint32_t maxCount; + if (aFlags & EXCLUDE_SHORTHANDS) { + maxCount = eCSSProperty_COUNT_no_shorthands; + } else { + maxCount = eCSSProperty_COUNT; + } + + if (aFlags & INCLUDE_ALIASES) { + maxCount += (eCSSProperty_COUNT_with_aliases - eCSSProperty_COUNT); + } + + char16_t** props = + static_cast<char16_t**>(moz_xmalloc(maxCount * sizeof(char16_t*))); + +#define DO_PROP(_prop) \ + PR_BEGIN_MACRO \ + nsCSSPropertyID cssProp = nsCSSPropertyID(_prop); \ + if (nsCSSProps::IsEnabled(cssProp, CSSEnabledState::eForAllContent)) { \ + props[propCount] = \ + ToNewUnicode(nsDependentCString(kCSSRawProperties[_prop])); \ + ++propCount; \ + } \ + PR_END_MACRO + + // prop is the property id we're considering; propCount is how many properties + // we've put into props so far. + uint32_t prop = 0, propCount = 0; + for ( ; prop < eCSSProperty_COUNT_no_shorthands; ++prop) { + if (nsCSSProps::PropertyParseType(nsCSSPropertyID(prop)) != + CSS_PROPERTY_PARSE_INACCESSIBLE) { + DO_PROP(prop); + } + } + + if (!(aFlags & EXCLUDE_SHORTHANDS)) { + for ( ; prop < eCSSProperty_COUNT; ++prop) { + // Some shorthands are also aliases + if ((aFlags & INCLUDE_ALIASES) || + !nsCSSProps::PropHasFlags(nsCSSPropertyID(prop), + CSS_PROPERTY_IS_ALIAS)) { + DO_PROP(prop); + } + } + } + + if (aFlags & INCLUDE_ALIASES) { + for (prop = eCSSProperty_COUNT; prop < eCSSProperty_COUNT_with_aliases; ++prop) { + DO_PROP(prop); + } + } + +#undef DO_PROP + + *aCount = propCount; + *aProps = props; + + return NS_OK; +} + +static void InsertNoDuplicates(nsTArray<nsString>& aArray, + const nsAString& aString) +{ + size_t i = aArray.IndexOfFirstElementGt(aString); + if (i > 0 && aArray[i-1].Equals(aString)) { + return; + } + aArray.InsertElementAt(i, aString); +} + +static void GetKeywordsForProperty(const nsCSSPropertyID aProperty, + nsTArray<nsString>& aArray) +{ + if (nsCSSProps::IsShorthand(aProperty)) { + // Shorthand props have no keywords. + return; + } + const nsCSSProps::KTableEntry* keywordTable = + nsCSSProps::kKeywordTableTable[aProperty]; + if (keywordTable) { + for (size_t i = 0; keywordTable[i].mKeyword != eCSSKeyword_UNKNOWN; ++i) { + nsCSSKeyword word = keywordTable[i].mKeyword; + InsertNoDuplicates(aArray, + NS_ConvertASCIItoUTF16(nsCSSKeywords::GetStringValue(word))); + } + } +} + +static void GetColorsForProperty(const uint32_t aParserVariant, + nsTArray<nsString>& aArray) +{ + if (aParserVariant & VARIANT_COLOR) { + // GetKeywordsForProperty and GetOtherValuesForProperty assume aArray is sorted, + // and if aArray is not empty here, then it's not going to be sorted coming out. + MOZ_ASSERT(aArray.Length() == 0); + size_t size; + const char * const *allColorNames = NS_AllColorNames(&size); + nsString* utf16Names = aArray.AppendElements(size); + for (size_t i = 0; i < size; i++) { + CopyASCIItoUTF16(allColorNames[i], utf16Names[i]); + } + InsertNoDuplicates(aArray, NS_LITERAL_STRING("currentColor")); + } + return; +} + +static void GetOtherValuesForProperty(const uint32_t aParserVariant, + nsTArray<nsString>& aArray) +{ + if (aParserVariant & VARIANT_AUTO) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("auto")); + } + if (aParserVariant & VARIANT_NORMAL) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("normal")); + } + if(aParserVariant & VARIANT_ALL) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("all")); + } + if (aParserVariant & VARIANT_NONE) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("none")); + } + if (aParserVariant & VARIANT_ELEMENT) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-element")); + } + if (aParserVariant & VARIANT_IMAGE_RECT) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-image-rect")); + } + if (aParserVariant & VARIANT_COLOR) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("rgb")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("hsl")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("rgba")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("hsla")); + } + if (aParserVariant & VARIANT_TIMING_FUNCTION) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("cubic-bezier")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("steps")); + } + if (aParserVariant & VARIANT_CALC) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("calc")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-calc")); + } + if (aParserVariant & VARIANT_URL) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("url")); + } + if (aParserVariant & VARIANT_GRADIENT) { + InsertNoDuplicates(aArray, NS_LITERAL_STRING("linear-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("radial-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("repeating-linear-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("repeating-radial-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-linear-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-radial-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-repeating-linear-gradient")); + InsertNoDuplicates(aArray, NS_LITERAL_STRING("-moz-repeating-radial-gradient")); + } +} + +NS_IMETHODIMP +inDOMUtils::GetSubpropertiesForCSSProperty(const nsAString& aProperty, + uint32_t* aLength, + char16_t*** aValues) +{ + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent); + + if (propertyID == eCSSProperty_UNKNOWN) { + return NS_ERROR_FAILURE; + } + + if (propertyID == eCSSPropertyExtra_variable) { + *aValues = static_cast<char16_t**>(moz_xmalloc(sizeof(char16_t*))); + (*aValues)[0] = ToNewUnicode(aProperty); + *aLength = 1; + return NS_OK; + } + + if (!nsCSSProps::IsShorthand(propertyID)) { + *aValues = static_cast<char16_t**>(moz_xmalloc(sizeof(char16_t*))); + (*aValues)[0] = ToNewUnicode(nsCSSProps::GetStringValue(propertyID)); + *aLength = 1; + return NS_OK; + } + + // Count up how many subproperties we have. + size_t subpropCount = 0; + for (const nsCSSPropertyID *props = nsCSSProps::SubpropertyEntryFor(propertyID); + *props != eCSSProperty_UNKNOWN; ++props) { + ++subpropCount; + } + + *aValues = + static_cast<char16_t**>(moz_xmalloc(subpropCount * sizeof(char16_t*))); + *aLength = subpropCount; + for (const nsCSSPropertyID *props = nsCSSProps::SubpropertyEntryFor(propertyID), + *props_start = props; + *props != eCSSProperty_UNKNOWN; ++props) { + (*aValues)[props-props_start] = ToNewUnicode(nsCSSProps::GetStringValue(*props)); + } + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::CssPropertyIsShorthand(const nsAString& aProperty, bool *_retval) +{ + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent); + if (propertyID == eCSSProperty_UNKNOWN) { + return NS_ERROR_FAILURE; + } + + if (propertyID == eCSSPropertyExtra_variable) { + *_retval = false; + } else { + *_retval = nsCSSProps::IsShorthand(propertyID); + } + return NS_OK; +} + +// A helper function that determines whether the given property +// supports the given type. +static bool +PropertySupportsVariant(nsCSSPropertyID aPropertyID, uint32_t aVariant) +{ + if (nsCSSProps::IsShorthand(aPropertyID)) { + // We need a special case for border here, because while it resets + // border-image, it can't actually parse an image. + if (aPropertyID == eCSSProperty_border) { + return (aVariant & (VARIANT_COLOR | VARIANT_LENGTH)) != 0; + } + + for (const nsCSSPropertyID* props = nsCSSProps::SubpropertyEntryFor(aPropertyID); + *props != eCSSProperty_UNKNOWN; ++props) { + if (PropertySupportsVariant(*props, aVariant)) { + return true; + } + } + return false; + } + + // Properties that are parsed by functions must have their + // attributes hand-maintained here. + if (nsCSSProps::PropHasFlags(aPropertyID, CSS_PROPERTY_VALUE_PARSER_FUNCTION) || + nsCSSProps::PropertyParseType(aPropertyID) == CSS_PROPERTY_PARSE_FUNCTION) { + // These must all be special-cased. + uint32_t supported; + switch (aPropertyID) { + case eCSSProperty_border_image_slice: + case eCSSProperty_grid_template: + case eCSSProperty_grid: + supported = VARIANT_PN; + break; + + case eCSSProperty_border_image_outset: + supported = VARIANT_LN; + break; + + case eCSSProperty_border_image_width: + case eCSSProperty_stroke_dasharray: + supported = VARIANT_LPN; + break; + + case eCSSProperty_border_top_left_radius: + case eCSSProperty_border_top_right_radius: + case eCSSProperty_border_bottom_left_radius: + case eCSSProperty_border_bottom_right_radius: + case eCSSProperty_background_position: + case eCSSProperty_background_position_x: + case eCSSProperty_background_position_y: + case eCSSProperty_background_size: +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + case eCSSProperty_mask_position: + case eCSSProperty_mask_position_x: + case eCSSProperty_mask_position_y: + case eCSSProperty_mask_size: +#endif + case eCSSProperty_grid_auto_columns: + case eCSSProperty_grid_auto_rows: + case eCSSProperty_grid_template_columns: + case eCSSProperty_grid_template_rows: + case eCSSProperty_object_position: + case eCSSProperty_scroll_snap_coordinate: + case eCSSProperty_scroll_snap_destination: + case eCSSProperty_transform_origin: + case eCSSProperty_perspective_origin: + case eCSSProperty__moz_outline_radius_topLeft: + case eCSSProperty__moz_outline_radius_topRight: + case eCSSProperty__moz_outline_radius_bottomLeft: + case eCSSProperty__moz_outline_radius_bottomRight: + supported = VARIANT_LP; + break; + + case eCSSProperty_border_bottom_colors: + case eCSSProperty_border_left_colors: + case eCSSProperty_border_right_colors: + case eCSSProperty_border_top_colors: + supported = VARIANT_COLOR; + break; + + case eCSSProperty_text_shadow: + case eCSSProperty_box_shadow: + supported = VARIANT_LENGTH | VARIANT_COLOR; + break; + + case eCSSProperty_border_spacing: + supported = VARIANT_LENGTH; + break; + + case eCSSProperty_content: + case eCSSProperty_cursor: + case eCSSProperty_clip_path: + case eCSSProperty_shape_outside: + supported = VARIANT_URL; + break; + + case eCSSProperty_fill: + case eCSSProperty_stroke: + supported = VARIANT_COLOR | VARIANT_URL; + break; + + case eCSSProperty_image_orientation: + supported = VARIANT_ANGLE; + break; + + case eCSSProperty_filter: + supported = VARIANT_URL; + break; + + case eCSSProperty_grid_column_start: + case eCSSProperty_grid_column_end: + case eCSSProperty_grid_row_start: + case eCSSProperty_grid_row_end: + case eCSSProperty_font_weight: + case eCSSProperty_initial_letter: + supported = VARIANT_NUMBER; + break; + + default: + supported = 0; + break; + } + + return (supported & aVariant) != 0; + } + + return (nsCSSProps::ParserVariant(aPropertyID) & aVariant) != 0; +} + +NS_IMETHODIMP +inDOMUtils::CssPropertySupportsType(const nsAString& aProperty, uint32_t aType, + bool *_retval) +{ + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent); + if (propertyID == eCSSProperty_UNKNOWN) { + return NS_ERROR_FAILURE; + } + + if (propertyID >= eCSSProperty_COUNT) { + *_retval = false; + return NS_OK; + } + + uint32_t variant; + switch (aType) { + case TYPE_LENGTH: + variant = VARIANT_LENGTH; + break; + case TYPE_PERCENTAGE: + variant = VARIANT_PERCENT; + break; + case TYPE_COLOR: + variant = VARIANT_COLOR; + break; + case TYPE_URL: + variant = VARIANT_URL; + break; + case TYPE_ANGLE: + variant = VARIANT_ANGLE; + break; + case TYPE_FREQUENCY: + variant = VARIANT_FREQUENCY; + break; + case TYPE_TIME: + variant = VARIANT_TIME; + break; + case TYPE_GRADIENT: + variant = VARIANT_GRADIENT; + break; + case TYPE_TIMING_FUNCTION: + variant = VARIANT_TIMING_FUNCTION; + break; + case TYPE_IMAGE_RECT: + variant = VARIANT_IMAGE_RECT; + break; + case TYPE_NUMBER: + // Include integers under "number"? + variant = VARIANT_NUMBER | VARIANT_INTEGER; + break; + default: + // Unknown type + return NS_ERROR_NOT_AVAILABLE; + } + + *_retval = PropertySupportsVariant(propertyID, variant); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetCSSValuesForProperty(const nsAString& aProperty, + uint32_t* aLength, + char16_t*** aValues) +{ + nsCSSPropertyID propertyID = nsCSSProps:: + LookupProperty(aProperty, CSSEnabledState::eForAllContent); + if (propertyID == eCSSProperty_UNKNOWN) { + return NS_ERROR_FAILURE; + } + + nsTArray<nsString> array; + // We start collecting the values, BUT colors need to go in first, because array + // needs to stay sorted, and the colors are sorted, so we just append them. + if (propertyID == eCSSPropertyExtra_variable) { + // No other values we can report. + } else if (!nsCSSProps::IsShorthand(propertyID)) { + // Property is longhand. + uint32_t propertyParserVariant = nsCSSProps::ParserVariant(propertyID); + // Get colors first. + GetColorsForProperty(propertyParserVariant, array); + if (propertyParserVariant & VARIANT_KEYWORD) { + GetKeywordsForProperty(propertyID, array); + } + GetOtherValuesForProperty(propertyParserVariant, array); + } else { + // Property is shorthand. + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subproperty, propertyID, + CSSEnabledState::eForAllContent) { + // Get colors (once) first. + uint32_t propertyParserVariant = nsCSSProps::ParserVariant(*subproperty); + if (propertyParserVariant & VARIANT_COLOR) { + GetColorsForProperty(propertyParserVariant, array); + break; + } + } + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subproperty, propertyID, + CSSEnabledState::eForAllContent) { + uint32_t propertyParserVariant = nsCSSProps::ParserVariant(*subproperty); + if (propertyParserVariant & VARIANT_KEYWORD) { + GetKeywordsForProperty(*subproperty, array); + } + GetOtherValuesForProperty(propertyParserVariant, array); + } + } + // All CSS properties take initial, inherit and unset. + InsertNoDuplicates(array, NS_LITERAL_STRING("initial")); + InsertNoDuplicates(array, NS_LITERAL_STRING("inherit")); + InsertNoDuplicates(array, NS_LITERAL_STRING("unset")); + + *aLength = array.Length(); + char16_t** ret = + static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*))); + for (uint32_t i = 0; i < *aLength; ++i) { + ret[i] = ToNewUnicode(array[i]); + } + *aValues = ret; + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::ColorNameToRGB(const nsAString& aColorName, JSContext* aCx, + JS::MutableHandle<JS::Value> aValue) +{ + nscolor color; + if (!NS_ColorNameToRGB(aColorName, &color)) { + return NS_ERROR_INVALID_ARG; + } + + InspectorRGBTriple triple; + triple.mR = NS_GET_R(color); + triple.mG = NS_GET_G(color); + triple.mB = NS_GET_B(color); + + if (!ToJSValue(aCx, triple, aValue)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::RgbToColorName(uint8_t aR, uint8_t aG, uint8_t aB, + nsAString& aColorName) +{ + const char* color = NS_RGBToColorName(NS_RGB(aR, aG, aB)); + if (!color) { + aColorName.Truncate(); + return NS_ERROR_INVALID_ARG; + } + + aColorName.AssignASCII(color); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::ColorToRGBA(const nsAString& aColorString, JSContext* aCx, + JS::MutableHandle<JS::Value> aValue) +{ + nscolor color = 0; + nsCSSParser cssParser; + nsCSSValue cssValue; + + bool isColor = cssParser.ParseColorString(aColorString, nullptr, 0, + cssValue, true); + + if (!isColor) { + aValue.setNull(); + return NS_OK; + } + + nsRuleNode::ComputeColor(cssValue, nullptr, nullptr, color); + + InspectorRGBATuple tuple; + 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)); + + if (!ToJSValue(aCx, tuple, aValue)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::IsValidCSSColor(const nsAString& aColorString, bool *_retval) +{ + nsCSSParser cssParser; + nsCSSValue cssValue; + *_retval = cssParser.ParseColorString(aColorString, nullptr, 0, cssValue, true); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::CssPropertyIsValid(const nsAString& aPropertyName, + const nsAString& aPropertyValue, + bool *_retval) +{ + nsCSSPropertyID propertyID = nsCSSProps:: + LookupProperty(aPropertyName, CSSEnabledState::eIgnoreEnabledState); + + if (propertyID == eCSSProperty_UNKNOWN) { + *_retval = false; + return NS_OK; + } + + if (propertyID == eCSSPropertyExtra_variable) { + *_retval = true; + return NS_OK; + } + + // Get a parser, parse the property. + nsCSSParser parser; + *_retval = parser.IsValueValidForProperty(propertyID, aPropertyValue); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetBindingURLs(nsIDOMElement *aElement, nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(aElement); + + *_retval = nullptr; + + nsCOMPtr<nsIMutableArray> urls = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (!urls) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(content); + + nsXBLBinding *binding = content->GetXBLBinding(); + + while (binding) { + urls->AppendElement(binding->PrototypeBinding()->BindingURI(), false); + binding = binding->GetBaseBinding(); + } + + urls.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::SetContentState(nsIDOMElement* aElement, + EventStates::InternalType aState, + bool* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aElement); + + RefPtr<EventStateManager> esm = + inLayoutUtils::GetEventStateManagerFor(aElement); + NS_ENSURE_TRUE(esm, NS_ERROR_INVALID_ARG); + + nsCOMPtr<nsIContent> content; + content = do_QueryInterface(aElement); + NS_ENSURE_TRUE(content, NS_ERROR_INVALID_ARG); + + *aRetVal = esm->SetContentState(content, EventStates(aState)); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::RemoveContentState(nsIDOMElement* aElement, + EventStates::InternalType aState, + bool* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aElement); + + RefPtr<EventStateManager> esm = + inLayoutUtils::GetEventStateManagerFor(aElement); + NS_ENSURE_TRUE(esm, NS_ERROR_INVALID_ARG); + + *aRetVal = esm->SetContentState(nullptr, EventStates(aState)); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetContentState(nsIDOMElement* aElement, + EventStates::InternalType* aState) +{ + *aState = 0; + nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(content); + + // NOTE: if this method is removed, + // please remove GetInternalValue from EventStates + *aState = content->AsElement()->State().GetInternalValue(); + return NS_OK; +} + +/* static */ nsresult +inDOMUtils::GetRuleNodeForElement(dom::Element* aElement, + nsIAtom* aPseudo, + nsStyleContext** aStyleContext, + nsRuleNode** aRuleNode) +{ + MOZ_ASSERT(aElement); + + *aRuleNode = nullptr; + *aStyleContext = nullptr; + + nsIDocument* doc = aElement->GetComposedDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); + + nsIPresShell *presShell = doc->GetShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_UNEXPECTED); + + nsPresContext *presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_UNEXPECTED); + + presContext->EnsureSafeToHandOutCSSRules(); + + RefPtr<nsStyleContext> sContext = + nsComputedDOMStyle::GetStyleContextForElement(aElement, aPseudo, presShell); + if (sContext) { + *aRuleNode = sContext->RuleNode(); + sContext.forget(aStyleContext); + } + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::GetUsedFontFaces(nsIDOMRange* aRange, + nsIDOMFontFaceList** aFontFaceList) +{ + return static_cast<nsRange*>(aRange)->GetUsedFontFaces(aFontFaceList); +} + +static EventStates +GetStatesForPseudoClass(const nsAString& aStatePseudo) +{ + // An array of the states that are relevant for various pseudoclasses. + // XXXbz this duplicates code in nsCSSRuleProcessor + static const EventStates sPseudoClassStates[] = { +#define CSS_PSEUDO_CLASS(_name, _value, _flags, _pref) \ + EventStates(), +#define CSS_STATE_PSEUDO_CLASS(_name, _value, _flags, _pref, _states) \ + _states, +#include "nsCSSPseudoClassList.h" +#undef CSS_STATE_PSEUDO_CLASS +#undef CSS_PSEUDO_CLASS + + // Add more entries for our fake values to make sure we can't + // index out of bounds into this array no matter what. + EventStates(), + EventStates() + }; + static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) == + static_cast<size_t>(CSSPseudoClassType::MAX), + "Length of PseudoClassStates array is incorrect"); + + nsCOMPtr<nsIAtom> atom = NS_Atomize(aStatePseudo); + CSSPseudoClassType type = nsCSSPseudoClasses:: + GetPseudoType(atom, CSSEnabledState::eIgnoreEnabledState); + + // Ignore :any-link so we don't give the element simultaneous + // visited and unvisited style state + if (type == CSSPseudoClassType::anyLink || + type == CSSPseudoClassType::mozAnyLink) { + return EventStates(); + } + // Our array above is long enough that indexing into it with + // NotPseudo is ok. + return sPseudoClassStates[static_cast<CSSPseudoClassTypeBase>(type)]; +} + +NS_IMETHODIMP +inDOMUtils::GetCSSPseudoElementNames(uint32_t* aLength, char16_t*** aNames) +{ + nsTArray<nsIAtom*> array; + + const CSSPseudoElementTypeBase pseudoCount = + static_cast<CSSPseudoElementTypeBase>(CSSPseudoElementType::Count); + for (CSSPseudoElementTypeBase i = 0; i < pseudoCount; ++i) { + CSSPseudoElementType type = static_cast<CSSPseudoElementType>(i); + if (nsCSSPseudoElements::IsEnabled(type, CSSEnabledState::eForAllContent)) { + nsIAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type); + array.AppendElement(atom); + } + } + + *aLength = array.Length(); + char16_t** ret = + static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*))); + for (uint32_t i = 0; i < *aLength; ++i) { + ret[i] = ToNewUnicode(nsDependentAtomString(array[i])); + } + *aNames = ret; + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement, + const nsAString &aPseudoClass) +{ + EventStates state = GetStatesForPseudoClass(aPseudoClass); + if (state.IsEmpty()) { + return NS_OK; + } + + nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(element); + + element->LockStyleStates(state); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::RemovePseudoClassLock(nsIDOMElement *aElement, + const nsAString &aPseudoClass) +{ + EventStates state = GetStatesForPseudoClass(aPseudoClass); + if (state.IsEmpty()) { + return NS_OK; + } + + nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(element); + + element->UnlockStyleStates(state); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::HasPseudoClassLock(nsIDOMElement *aElement, + const nsAString &aPseudoClass, + bool *_retval) +{ + EventStates state = GetStatesForPseudoClass(aPseudoClass); + if (state.IsEmpty()) { + *_retval = false; + return NS_OK; + } + + nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(element); + + EventStates locks = element->LockedStyleStates(); + + *_retval = locks.HasAllStates(state); + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::ClearPseudoClassLocks(nsIDOMElement *aElement) +{ + nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(element); + + element->ClearStyleStateLocks(); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMUtils::ParseStyleSheet(nsIDOMCSSStyleSheet *aSheet, + const nsAString& aInput) +{ + RefPtr<CSSStyleSheet> sheet = do_QueryObject(aSheet); + NS_ENSURE_ARG_POINTER(sheet); + + return sheet->ReparseSheet(aInput); +} + +NS_IMETHODIMP +inDOMUtils::ScrollElementIntoView(nsIDOMElement *aElement) +{ + nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); + NS_ENSURE_ARG_POINTER(content); + + nsIPresShell* presShell = content->OwnerDoc()->GetShell(); + if (!presShell) { + return NS_OK; + } + + presShell->ScrollContentIntoView(content, + nsIPresShell::ScrollAxis(), + nsIPresShell::ScrollAxis(), + nsIPresShell::SCROLL_OVERFLOW_HIDDEN); + + return NS_OK; +} diff --git a/layout/inspector/inDOMUtils.h b/layout/inspector/inDOMUtils.h new file mode 100644 index 000000000..e12915d16 --- /dev/null +++ b/layout/inspector/inDOMUtils.h @@ -0,0 +1,42 @@ +/* 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 __inDOMUtils_h__ +#define __inDOMUtils_h__ + +#include "inIDOMUtils.h" + +class nsRuleNode; +class nsStyleContext; +class nsIAtom; + +namespace mozilla { +namespace dom { +class Element; +} // namespace dom +} // namespace mozilla + +class inDOMUtils final : public inIDOMUtils +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_INIDOMUTILS + + inDOMUtils(); + +private: + virtual ~inDOMUtils(); + + // aStyleContext must be released by the caller once he's done with aRuleNode. + static nsresult GetRuleNodeForElement(mozilla::dom::Element* aElement, + nsIAtom* aPseudo, + nsStyleContext** aStyleContext, + nsRuleNode** aRuleNode); +}; + +// {0a499822-a287-4089-ad3f-9ffcd4f40263} +#define IN_DOMUTILS_CID \ + {0x0a499822, 0xa287, 0x4089, {0xad, 0x3f, 0x9f, 0xfc, 0xd4, 0xf4, 0x02, 0x63}} + +#endif // __inDOMUtils_h__ diff --git a/layout/inspector/inDOMView.cpp b/layout/inspector/inDOMView.cpp new file mode 100644 index 000000000..967671ba3 --- /dev/null +++ b/layout/inspector/inDOMView.cpp @@ -0,0 +1,1283 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "inDOMView.h" +#include "inIDOMUtils.h" + +#include "inLayoutUtils.h" + +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeFilter.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMCharacterData.h" +#include "nsIDOMAttr.h" +#include "nsIDOMMozNamedAttrMap.h" +#include "nsIDOMMutationEvent.h" +#include "nsBindingManager.h" +#include "nsNameSpaceManager.h" +#include "nsIDocument.h" +#include "nsIServiceManager.h" +#include "nsITreeColumns.h" +#include "nsITreeBoxObject.h" +#include "mozilla/dom/Element.h" +#include "mozilla/Services.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////// +// inDOMViewNode + +class inDOMViewNode +{ +public: + inDOMViewNode() {} + explicit inDOMViewNode(nsIDOMNode* aNode); + ~inDOMViewNode(); + + nsCOMPtr<nsIDOMNode> node; + + inDOMViewNode* parent; + inDOMViewNode* next; + inDOMViewNode* previous; + + int32_t level; + bool isOpen; + bool isContainer; + bool hasAnonymous; + bool hasSubDocument; +}; + +inDOMViewNode::inDOMViewNode(nsIDOMNode* aNode) : + node(aNode), + parent(nullptr), + next(nullptr), + previous(nullptr), + level(0), + isOpen(false), + isContainer(false), + hasAnonymous(false), + hasSubDocument(false) +{ + +} + +inDOMViewNode::~inDOMViewNode() +{ +} + +//////////////////////////////////////////////////////////////////////// + +inDOMView::inDOMView() : + mShowAnonymous(false), + mShowSubDocuments(false), + mShowWhitespaceNodes(true), + mShowAccessibleNodes(false), + mWhatToShow(nsIDOMNodeFilter::SHOW_ALL) +{ +} + +inDOMView::~inDOMView() +{ + SetRootNode(nullptr); +} + + +//////////////////////////////////////////////////////////////////////// +// nsISupports + +NS_IMPL_ISUPPORTS(inDOMView, + inIDOMView, + nsITreeView, + nsIMutationObserver) + +//////////////////////////////////////////////////////////////////////// +// inIDOMView + +NS_IMETHODIMP +inDOMView::GetRootNode(nsIDOMNode** aNode) +{ + *aNode = mRootNode; + NS_IF_ADDREF(*aNode); + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetRootNode(nsIDOMNode* aNode) +{ + if (mTree) + mTree->BeginUpdateBatch(); + + if (mRootDocument) { + // remove previous document observer + nsCOMPtr<nsINode> doc(do_QueryInterface(mRootDocument)); + if (doc) + doc->RemoveMutationObserver(this); + } + + RemoveAllNodes(); + + mRootNode = aNode; + + if (aNode) { + // If we are able to show element nodes, then start with the root node + // as the first node in the buffer + if (mWhatToShow & nsIDOMNodeFilter::SHOW_ELEMENT) { + // allocate new node array + AppendNode(CreateNode(aNode, nullptr)); + } else { + // place only the children of the root node in the buffer + ExpandNode(-1); + } + + // store an owning reference to document so that it isn't + // destroyed before we are + mRootDocument = do_QueryInterface(aNode); + if (!mRootDocument) { + aNode->GetOwnerDocument(getter_AddRefs(mRootDocument)); + } + + // add document observer + nsCOMPtr<nsINode> doc(do_QueryInterface(mRootDocument)); + if (doc) + doc->AddMutationObserver(this); + } else { + mRootDocument = nullptr; + } + + if (mTree) + mTree->EndUpdateBatch(); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetNodeFromRowIndex(int32_t rowIndex, nsIDOMNode **_retval) +{ + inDOMViewNode* viewNode = nullptr; + RowToNode(rowIndex, &viewNode); + if (!viewNode) return NS_ERROR_FAILURE; + *_retval = viewNode->node; + NS_IF_ADDREF(*_retval); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetRowIndexFromNode(nsIDOMNode *node, int32_t *_retval) +{ + NodeToRow(node, _retval); + return NS_OK; +} + + +NS_IMETHODIMP +inDOMView::GetShowAnonymousContent(bool *aShowAnonymousContent) +{ + *aShowAnonymousContent = mShowAnonymous; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetShowAnonymousContent(bool aShowAnonymousContent) +{ + mShowAnonymous = aShowAnonymousContent; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetShowSubDocuments(bool *aShowSubDocuments) +{ + *aShowSubDocuments = mShowSubDocuments; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetShowSubDocuments(bool aShowSubDocuments) +{ + mShowSubDocuments = aShowSubDocuments; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetShowWhitespaceNodes(bool *aShowWhitespaceNodes) +{ + *aShowWhitespaceNodes = mShowWhitespaceNodes; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetShowWhitespaceNodes(bool aShowWhitespaceNodes) +{ + mShowWhitespaceNodes = aShowWhitespaceNodes; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetShowAccessibleNodes(bool *aShowAccessibleNodes) +{ + *aShowAccessibleNodes = mShowAccessibleNodes; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetShowAccessibleNodes(bool aShowAccessibleNodes) +{ + mShowAccessibleNodes = aShowAccessibleNodes; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetWhatToShow(uint32_t *aWhatToShow) +{ + *aWhatToShow = mWhatToShow; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetWhatToShow(uint32_t aWhatToShow) +{ + mWhatToShow = aWhatToShow; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::Rebuild() +{ + nsCOMPtr<nsIDOMNode> root; + GetRootNode(getter_AddRefs(root)); + SetRootNode(root); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// nsITreeView + +NS_IMETHODIMP +inDOMView::GetRowCount(int32_t *aRowCount) +{ + *aRowCount = GetRowCount(); + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetRowProperties(int32_t index, nsAString& aProps) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetCellProperties(int32_t row, nsITreeColumn* col, + nsAString& aProps) +{ + inDOMViewNode* node = nullptr; + RowToNode(row, &node); + if (!node) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIContent> content = do_QueryInterface(node->node); + if (content && content->IsInAnonymousSubtree()) { + aProps.AppendLiteral("anonymous "); + } + + uint16_t nodeType; + node->node->GetNodeType(&nodeType); + switch (nodeType) { + case nsIDOMNode::ELEMENT_NODE: + aProps.AppendLiteral("ELEMENT_NODE"); + break; + case nsIDOMNode::ATTRIBUTE_NODE: + aProps.AppendLiteral("ATTRIBUTE_NODE"); + break; + case nsIDOMNode::TEXT_NODE: + aProps.AppendLiteral("TEXT_NODE"); + break; + case nsIDOMNode::CDATA_SECTION_NODE: + aProps.AppendLiteral("CDATA_SECTION_NODE"); + break; + case nsIDOMNode::ENTITY_REFERENCE_NODE: + aProps.AppendLiteral("ENTITY_REFERENCE_NODE"); + break; + case nsIDOMNode::ENTITY_NODE: + aProps.AppendLiteral("ENTITY_NODE"); + break; + case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: + aProps.AppendLiteral("PROCESSING_INSTRUCTION_NODE"); + break; + case nsIDOMNode::COMMENT_NODE: + aProps.AppendLiteral("COMMENT_NODE"); + break; + case nsIDOMNode::DOCUMENT_NODE: + aProps.AppendLiteral("DOCUMENT_NODE"); + break; + case nsIDOMNode::DOCUMENT_TYPE_NODE: + aProps.AppendLiteral("DOCUMENT_TYPE_NODE"); + break; + case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: + aProps.AppendLiteral("DOCUMENT_FRAGMENT_NODE"); + break; + case nsIDOMNode::NOTATION_NODE: + aProps.AppendLiteral("NOTATION_NODE"); + break; + } + +#ifdef ACCESSIBILITY + if (mShowAccessibleNodes) { + nsAccessibilityService* accService = GetOrCreateAccService(); + NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE); + + if (accService->HasAccessible(node->node)) + aProps.AppendLiteral(" ACCESSIBLE_NODE"); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetColumnProperties(nsITreeColumn* col, nsAString& aProps) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(row, &node); + if (!node) return NS_ERROR_FAILURE; + + nsIDOMNode* domNode = node->node; + + nsAutoString colID; + col->GetId(colID); + if (colID.EqualsLiteral("colNodeName")) + domNode->GetNodeName(_retval); + else if (colID.EqualsLiteral("colLocalName")) + domNode->GetLocalName(_retval); + else if (colID.EqualsLiteral("colPrefix")) + domNode->GetPrefix(_retval); + else if (colID.EqualsLiteral("colNamespaceURI")) + domNode->GetNamespaceURI(_retval); + else if (colID.EqualsLiteral("colNodeType")) { + uint16_t nodeType; + domNode->GetNodeType(&nodeType); + nsAutoString temp; + temp.AppendInt(int32_t(nodeType)); + _retval = temp; + } else if (colID.EqualsLiteral("colNodeValue")) + domNode->GetNodeValue(_retval); + else { + if (StringBeginsWith(colID, NS_LITERAL_STRING("col@"))) { + nsCOMPtr<nsIDOMElement> el = do_QueryInterface(node->node); + if (el) { + nsAutoString attr; + colID.Right(attr, colID.Length()-4); // have to use this because Substring is crashing on me! + el->GetAttribute(attr, _retval); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::IsContainer(int32_t index, bool *_retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(index, &node); + if (!node) return NS_ERROR_FAILURE; + + *_retval = node->isContainer; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::IsContainerOpen(int32_t index, bool *_retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(index, &node); + if (!node) return NS_ERROR_FAILURE; + + *_retval = node->isOpen; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::IsContainerEmpty(int32_t index, bool *_retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(index, &node); + if (!node) return NS_ERROR_FAILURE; + + *_retval = node->isContainer ? false : true; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetLevel(int32_t index, int32_t *_retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(index, &node); + if (!node) return NS_ERROR_FAILURE; + + *_retval = node->level; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetParentIndex(int32_t rowIndex, int32_t *_retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(rowIndex, &node); + if (!node) return NS_ERROR_FAILURE; + + // GetParentIndex returns -1 if there is no parent + *_retval = -1; + + inDOMViewNode* checkNode = nullptr; + int32_t i = rowIndex - 1; + do { + nsresult rv = RowToNode(i, &checkNode); + if (NS_FAILED(rv)) { + // No parent. Just break out. + break; + } + + if (checkNode == node->parent) { + *_retval = i; + return NS_OK; + } + --i; + } while (checkNode); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval) +{ + inDOMViewNode* node = nullptr; + RowToNode(rowIndex, &node); + if (!node) return NS_ERROR_FAILURE; + + *_retval = node->next != nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::ToggleOpenState(int32_t index) +{ + inDOMViewNode* node = nullptr; + RowToNode(index, &node); + if (!node) return NS_ERROR_FAILURE; + + int32_t oldCount = GetRowCount(); + if (node->isOpen) + CollapseNode(index); + else + ExpandNode(index); + + // Update the twisty. + mTree->InvalidateRow(index); + + mTree->RowCountChanged(index+1, GetRowCount() - oldCount); + + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetTree(nsITreeBoxObject *tree) +{ + mTree = tree; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::GetSelection(nsITreeSelection * *aSelection) +{ + *aSelection = mSelection; + NS_IF_ADDREF(*aSelection); + return NS_OK; +} + +NS_IMETHODIMP inDOMView::SetSelection(nsITreeSelection * aSelection) +{ + mSelection = aSelection; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SelectionChanged() +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::CycleHeader(nsITreeColumn* col) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::CycleCell(int32_t row, nsITreeColumn* col) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval) +{ + return NS_OK; +} + + +NS_IMETHODIMP +inDOMView::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::IsSeparator(int32_t index, bool *_retval) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::IsSorted(bool *_retval) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::CanDrop(int32_t index, int32_t orientation, + nsIDOMDataTransfer* aDataTransfer, bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::Drop(int32_t row, int32_t orientation, nsIDOMDataTransfer* aDataTransfer) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::PerformAction(const char16_t *action) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::PerformActionOnRow(const char16_t *action, int32_t row) +{ + return NS_OK; +} + +NS_IMETHODIMP +inDOMView::PerformActionOnCell(const char16_t* action, int32_t row, nsITreeColumn* col) +{ + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////// +// nsIMutationObserver + +void +inDOMView::NodeWillBeDestroyed(const nsINode* aNode) +{ + NS_NOTREACHED("Document destroyed while we're holding a strong ref to it"); +} + +void +inDOMView::AttributeChanged(nsIDocument* aDocument, dom::Element* aElement, + int32_t aNameSpaceID, nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + if (!mTree) { + return; + } + + if (!(mWhatToShow & nsIDOMNodeFilter::SHOW_ATTRIBUTE)) { + return; + } + + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + + // get the dom attribute node, if there is any + nsCOMPtr<nsIDOMElement> el(do_QueryInterface(aElement)); + nsCOMPtr<nsIDOMAttr> domAttr; + nsDependentAtomString attrStr(aAttribute); + if (aNameSpaceID) { + nsNameSpaceManager* nsm = nsNameSpaceManager::GetInstance(); + if (!nsm) { + // we can't find out which attribute we want :( + return; + } + nsString attrNS; + nsresult rv = nsm->GetNameSpaceURI(aNameSpaceID, attrNS); + if (NS_FAILED(rv)) { + return; + } + (void)el->GetAttributeNodeNS(attrNS, attrStr, getter_AddRefs(domAttr)); + } else { + (void)el->GetAttributeNode(attrStr, getter_AddRefs(domAttr)); + } + + if (aModType == nsIDOMMutationEvent::MODIFICATION) { + // No fancy stuff here, just invalidate the changed row + if (!domAttr) { + return; + } + int32_t row = 0; + NodeToRow(domAttr, &row); + mTree->InvalidateRange(row, row); + } else if (aModType == nsIDOMMutationEvent::ADDITION) { + if (!domAttr) { + return; + } + // get the number of attributes on this content node + nsCOMPtr<nsIDOMMozNamedAttrMap> attrs; + el->GetAttributes(getter_AddRefs(attrs)); + uint32_t attrCount; + attrs->GetLength(&attrCount); + + inDOMViewNode* contentNode = nullptr; + int32_t contentRow; + int32_t attrRow; + if (mRootNode == el && + !(mWhatToShow & nsIDOMNodeFilter::SHOW_ELEMENT)) { + // if this view has a root node but is not displaying it, + // it is ok to act as if the changed attribute is on the root. + attrRow = attrCount - 1; + } else { + if (NS_FAILED(NodeToRow(el, &contentRow))) { + return; + } + RowToNode(contentRow, &contentNode); + if (!contentNode->isOpen) { + return; + } + attrRow = contentRow + attrCount; + } + + inDOMViewNode* newNode = CreateNode(domAttr, contentNode); + inDOMViewNode* insertNode = nullptr; + RowToNode(attrRow, &insertNode); + if (insertNode) { + if (contentNode && + insertNode->level <= contentNode->level) { + RowToNode(attrRow-1, &insertNode); + InsertLinkAfter(newNode, insertNode); + } else + InsertLinkBefore(newNode, insertNode); + } + InsertNode(newNode, attrRow); + mTree->RowCountChanged(attrRow, 1); + } else if (aModType == nsIDOMMutationEvent::REMOVAL) { + // At this point, the attribute is already gone from the DOM, but is still represented + // in our mRows array. Search through the content node's children for the corresponding + // node and remove it. + + // get the row of the content node + inDOMViewNode* contentNode = nullptr; + int32_t contentRow; + int32_t baseLevel; + if (NS_SUCCEEDED(NodeToRow(el, &contentRow))) { + RowToNode(contentRow, &contentNode); + baseLevel = contentNode->level; + } else { + if (mRootNode == el) { + contentRow = -1; + baseLevel = -1; + } else + return; + } + + // search for the attribute node that was removed + inDOMViewNode* checkNode = nullptr; + int32_t row = 0; + for (row = contentRow+1; row < GetRowCount(); ++row) { + checkNode = GetNodeAt(row); + if (checkNode->level == baseLevel+1) { + domAttr = do_QueryInterface(checkNode->node); + if (domAttr) { + nsAutoString attrName; + domAttr->GetNodeName(attrName); + if (attrName.Equals(attrStr)) { + // we have found the row for the attribute that was removed + RemoveLink(checkNode); + RemoveNode(row); + mTree->RowCountChanged(row, -1); + break; + } + } + } + if (checkNode->level <= baseLevel) + break; + } + + } +} + +void +inDOMView::ContentAppended(nsIDocument *aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t /* unused */) +{ + if (!mTree) { + return; + } + + for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { + // Our ContentInserted impl doesn't use the index + ContentInserted(aDocument, aContainer, cur, 0); + } +} + +void +inDOMView::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, + nsIContent* aChild, int32_t /* unused */) +{ + if (!mTree) + return; + + nsresult rv; + nsCOMPtr<nsIDOMNode> childDOMNode(do_QueryInterface(aChild)); + nsCOMPtr<nsIDOMNode> parent; + if (!mDOMUtils) { + mDOMUtils = services::GetInDOMUtils(); + if (!mDOMUtils) { + return; + } + } + mDOMUtils->GetParentForNode(childDOMNode, mShowAnonymous, + getter_AddRefs(parent)); + + // find the inDOMViewNode for the parent of the inserted content + int32_t parentRow = 0; + if (NS_FAILED(rv = NodeToRow(parent, &parentRow))) + return; + inDOMViewNode* parentNode = nullptr; + if (NS_FAILED(rv = RowToNode(parentRow, &parentNode))) + return; + + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + + if (!parentNode->isOpen) { + // Parent is not open, so don't bother creating tree rows for the + // kids. But do indicate that it's now a container, if needed. + if (!parentNode->isContainer) { + parentNode->isContainer = true; + mTree->InvalidateRow(parentRow); + } + return; + } + + // get the previous sibling of the inserted content + nsCOMPtr<nsIDOMNode> previous; + GetRealPreviousSibling(childDOMNode, parent, getter_AddRefs(previous)); + inDOMViewNode* previousNode = nullptr; + + int32_t row = 0; + if (previous) { + // find the inDOMViewNode for the previous sibling of the inserted content + int32_t previousRow = 0; + if (NS_FAILED(rv = NodeToRow(previous, &previousRow))) + return; + if (NS_FAILED(rv = RowToNode(previousRow, &previousNode))) + return; + + // get the last descendant of the previous row, which is the row + // after which to insert this new row + GetLastDescendantOf(previousNode, previousRow, &row); + ++row; + } else { + // there is no previous sibling, so the new row will be inserted after the parent + row = parentRow+1; + } + + inDOMViewNode* newNode = CreateNode(childDOMNode, parentNode); + + if (previous) { + InsertLinkAfter(newNode, previousNode); + } else { + int32_t firstChildRow; + if (NS_SUCCEEDED(GetFirstDescendantOf(parentNode, parentRow, &firstChildRow))) { + inDOMViewNode* firstChild; + RowToNode(firstChildRow, &firstChild); + InsertLinkBefore(newNode, firstChild); + } + } + + // insert new node + InsertNode(newNode, row); + + mTree->RowCountChanged(row, 1); +} + +void +inDOMView::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer, + nsIContent* aChild, int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + if (!mTree) + return; + + nsresult rv; + + // find the inDOMViewNode for the old child + nsCOMPtr<nsIDOMNode> oldDOMNode(do_QueryInterface(aChild)); + int32_t row = 0; + if (NS_FAILED(rv = NodeToRow(oldDOMNode, &row))) + return; + inDOMViewNode* oldNode; + if (NS_FAILED(rv = RowToNode(row, &oldNode))) + return; + + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + + // The parent may no longer be a container. Note that we don't want + // to access oldNode after calling RemoveNode, so do this now. + inDOMViewNode* parentNode = oldNode->parent; + bool isOnlyChild = oldNode->previous == nullptr && oldNode->next == nullptr; + + // Keep track of how many rows we are removing. It's at least one, + // but if we're open it's more. + int32_t oldCount = GetRowCount(); + + if (oldNode->isOpen) + CollapseNode(row); + + RemoveLink(oldNode); + RemoveNode(row); + + if (isOnlyChild) { + // Fix up the parent + parentNode->isContainer = false; + parentNode->isOpen = false; + mTree->InvalidateRow(NodeToRow(parentNode)); + } + + mTree->RowCountChanged(row, GetRowCount() - oldCount); +} + +/////////////////////////////////////////////////////////////////////// +// inDOMView + +//////// NODE MANAGEMENT + +inDOMViewNode* +inDOMView::GetNodeAt(int32_t aRow) +{ + return mNodes.ElementAt(aRow); +} + +int32_t +inDOMView::GetRowCount() +{ + return mNodes.Length(); +} + +int32_t +inDOMView::NodeToRow(inDOMViewNode* aNode) +{ + return mNodes.IndexOf(aNode); +} + +inDOMViewNode* +inDOMView::CreateNode(nsIDOMNode* aNode, inDOMViewNode* aParent) +{ + inDOMViewNode* viewNode = new inDOMViewNode(aNode); + viewNode->level = aParent ? aParent->level+1 : 0; + viewNode->parent = aParent; + + nsCOMArray<nsIDOMNode> grandKids; + GetChildNodesFor(aNode, grandKids); + viewNode->isContainer = (grandKids.Count() > 0); + return viewNode; +} + +bool +inDOMView::RowOutOfBounds(int32_t aRow, int32_t aCount) +{ + return aRow < 0 || aRow >= GetRowCount() || aCount+aRow > GetRowCount(); +} + +void +inDOMView::AppendNode(inDOMViewNode* aNode) +{ + mNodes.AppendElement(aNode); +} + +void +inDOMView::InsertNode(inDOMViewNode* aNode, int32_t aRow) +{ + if (RowOutOfBounds(aRow, 1)) + AppendNode(aNode); + else + mNodes.InsertElementAt(aRow, aNode); +} + +void +inDOMView::RemoveNode(int32_t aRow) +{ + if (RowOutOfBounds(aRow, 1)) + return; + + delete GetNodeAt(aRow); + mNodes.RemoveElementAt(aRow); +} + +void +inDOMView::ReplaceNode(inDOMViewNode* aNode, int32_t aRow) +{ + if (RowOutOfBounds(aRow, 1)) + return; + + delete GetNodeAt(aRow); + mNodes.ElementAt(aRow) = aNode; +} + +void +inDOMView::InsertNodes(nsTArray<inDOMViewNode*>& aNodes, int32_t aRow) +{ + if (aRow < 0 || aRow > GetRowCount()) + return; + + mNodes.InsertElementsAt(aRow, aNodes); +} + +void +inDOMView::RemoveNodes(int32_t aRow, int32_t aCount) +{ + if (aRow < 0) + return; + + int32_t rowCount = GetRowCount(); + for (int32_t i = aRow; i < aRow+aCount && i < rowCount; ++i) { + delete GetNodeAt(i); + } + + mNodes.RemoveElementsAt(aRow, aCount); +} + +void +inDOMView::RemoveAllNodes() +{ + int32_t rowCount = GetRowCount(); + for (int32_t i = 0; i < rowCount; ++i) { + delete GetNodeAt(i); + } + + mNodes.Clear(); +} + +void +inDOMView::ExpandNode(int32_t aRow) +{ + inDOMViewNode* node = nullptr; + RowToNode(aRow, &node); + + nsCOMArray<nsIDOMNode> kids; + GetChildNodesFor(node ? node->node : mRootNode, + kids); + int32_t kidCount = kids.Count(); + + nsTArray<inDOMViewNode*> list(kidCount); + + inDOMViewNode* newNode = nullptr; + inDOMViewNode* prevNode = nullptr; + + for (int32_t i = 0; i < kidCount; ++i) { + newNode = CreateNode(kids[i], node); + list.AppendElement(newNode); + + if (prevNode) + prevNode->next = newNode; + newNode->previous = prevNode; + prevNode = newNode; + } + + InsertNodes(list, aRow+1); + + if (node) + node->isOpen = true; +} + +void +inDOMView::CollapseNode(int32_t aRow) +{ + inDOMViewNode* node = nullptr; + nsresult rv = RowToNode(aRow, &node); + if (NS_FAILED(rv)) { + return; + } + + int32_t row = 0; + GetLastDescendantOf(node, aRow, &row); + + RemoveNodes(aRow+1, row-aRow); + + node->isOpen = false; +} + +//////// NODE AND ROW CONVERSION + +nsresult +inDOMView::RowToNode(int32_t aRow, inDOMViewNode** aNode) +{ + if (aRow < 0 || aRow >= GetRowCount()) + return NS_ERROR_FAILURE; + + *aNode = GetNodeAt(aRow); + return NS_OK; +} + +nsresult +inDOMView::NodeToRow(nsIDOMNode* aNode, int32_t* aRow) +{ + int32_t rowCount = GetRowCount(); + for (int32_t i = 0; i < rowCount; ++i) { + if (GetNodeAt(i)->node == aNode) { + *aRow = i; + return NS_OK; + } + } + + *aRow = -1; + return NS_ERROR_FAILURE; +} + +//////// NODE HIERARCHY MUTATION + +void +inDOMView::InsertLinkAfter(inDOMViewNode* aNode, inDOMViewNode* aInsertAfter) +{ + if (aInsertAfter->next) + aInsertAfter->next->previous = aNode; + aNode->next = aInsertAfter->next; + aInsertAfter->next = aNode; + aNode->previous = aInsertAfter; +} + +void +inDOMView::InsertLinkBefore(inDOMViewNode* aNode, inDOMViewNode* aInsertBefore) +{ + if (aInsertBefore->previous) + aInsertBefore->previous->next = aNode; + aNode->previous = aInsertBefore->previous; + aInsertBefore->previous = aNode; + aNode->next = aInsertBefore; +} + +void +inDOMView::RemoveLink(inDOMViewNode* aNode) +{ + if (aNode->previous) + aNode->previous->next = aNode->next; + if (aNode->next) + aNode->next->previous = aNode->previous; +} + +void +inDOMView::ReplaceLink(inDOMViewNode* aNewNode, inDOMViewNode* aOldNode) +{ + if (aOldNode->previous) + aOldNode->previous->next = aNewNode; + if (aOldNode->next) + aOldNode->next->previous = aNewNode; + aNewNode->next = aOldNode->next; + aNewNode->previous = aOldNode->previous; +} + +//////// NODE HIERARCHY UTILITIES + +nsresult +inDOMView::GetFirstDescendantOf(inDOMViewNode* aNode, int32_t aRow, int32_t* aResult) +{ + // get the first node that is a descendant of the previous sibling + int32_t row = 0; + inDOMViewNode* node; + for (row = aRow+1; row < GetRowCount(); ++row) { + node = GetNodeAt(row); + if (node->parent == aNode) { + *aResult = row; + return NS_OK; + } + if (node->level <= aNode->level) + break; + } + return NS_ERROR_FAILURE; +} + +nsresult +inDOMView::GetLastDescendantOf(inDOMViewNode* aNode, int32_t aRow, int32_t* aResult) +{ + // get the last node that is a descendant of the previous sibling + int32_t row = 0; + for (row = aRow+1; row < GetRowCount(); ++row) { + if (GetNodeAt(row)->level <= aNode->level) + break; + } + *aResult = row-1; + return NS_OK; +} + +//////// DOM UTILITIES + +nsresult +inDOMView::GetChildNodesFor(nsIDOMNode* aNode, nsCOMArray<nsIDOMNode>& aResult) +{ + NS_ENSURE_ARG(aNode); + // attribute nodes + if (mWhatToShow & nsIDOMNodeFilter::SHOW_ATTRIBUTE) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + if (element) { + nsCOMPtr<nsIDOMMozNamedAttrMap> attrs; + element->GetAttributes(getter_AddRefs(attrs)); + if (attrs) { + AppendAttrsToArray(attrs, aResult); + } + } + } + + if (mWhatToShow & nsIDOMNodeFilter::SHOW_ELEMENT) { + nsCOMPtr<nsIDOMNodeList> kids; + if (!mDOMUtils) { + mDOMUtils = services::GetInDOMUtils(); + if (!mDOMUtils) { + return NS_ERROR_FAILURE; + } + } + + mDOMUtils->GetChildrenForNode(aNode, mShowAnonymous, + getter_AddRefs(kids)); + + if (kids) { + AppendKidsToArray(kids, aResult); + } + } + + if (mShowSubDocuments) { + nsCOMPtr<nsIDOMNode> domdoc = + do_QueryInterface(inLayoutUtils::GetSubDocumentFor(aNode)); + if (domdoc) { + aResult.AppendObject(domdoc); + } + } + + return NS_OK; +} + +nsresult +inDOMView::GetRealPreviousSibling(nsIDOMNode* aNode, nsIDOMNode* aRealParent, nsIDOMNode** aSibling) +{ + // XXXjrh: This won't work for some cases during some situations where XBL insertion points + // are involved. Fix me! + aNode->GetPreviousSibling(aSibling); + return NS_OK; +} + +nsresult +inDOMView::AppendKidsToArray(nsIDOMNodeList* aKids, + nsCOMArray<nsIDOMNode>& aArray) +{ + uint32_t l = 0; + aKids->GetLength(&l); + nsCOMPtr<nsIDOMNode> kid; + uint16_t nodeType = 0; + + // Try and get DOM Utils in case we don't have one yet. + if (!mShowWhitespaceNodes && !mDOMUtils) { + mDOMUtils = services::GetInDOMUtils(); + } + + for (uint32_t i = 0; i < l; ++i) { + aKids->Item(i, getter_AddRefs(kid)); + kid->GetNodeType(&nodeType); + + NS_ASSERTION(nodeType && nodeType <= nsIDOMNode::NOTATION_NODE, + "Unknown node type. " + "Were new types added to the spec?"); + // As of DOM Level 2 Core and Traversal, each NodeFilter constant + // is defined as the lower nth bit in the NodeFilter bitmask, + // where n is the numeric constant of the nodeType it represents. + // If this invariant ever changes, we will need to update the + // following line. + uint32_t filterForNodeType = 1 << (nodeType - 1); + + if (mWhatToShow & filterForNodeType) { + if ((nodeType == nsIDOMNode::TEXT_NODE || + nodeType == nsIDOMNode::COMMENT_NODE) && + !mShowWhitespaceNodes && mDOMUtils) { + nsCOMPtr<nsIDOMCharacterData> data = do_QueryInterface(kid); + NS_ASSERTION(data, "Does not implement nsIDOMCharacterData!"); + bool ignore; + mDOMUtils->IsIgnorableWhitespace(data, &ignore); + if (ignore) { + continue; + } + } + + aArray.AppendElement(kid.forget()); + } + } + + return NS_OK; +} + +nsresult +inDOMView::AppendAttrsToArray(nsIDOMMozNamedAttrMap* aAttributes, + nsCOMArray<nsIDOMNode>& aArray) +{ + uint32_t l = 0; + aAttributes->GetLength(&l); + nsCOMPtr<nsIDOMAttr> attribute; + for (uint32_t i = 0; i < l; ++i) { + aAttributes->Item(i, getter_AddRefs(attribute)); + aArray.AppendElement(attribute.forget()); + } + return NS_OK; +} diff --git a/layout/inspector/inDOMView.h b/layout/inspector/inDOMView.h new file mode 100644 index 000000000..463d36316 --- /dev/null +++ b/layout/inspector/inDOMView.h @@ -0,0 +1,96 @@ +/* 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 __inDOMView_h__ +#define __inDOMView_h__ + +#include "inIDOMView.h" +#include "inIDOMUtils.h" + +#include "nsITreeView.h" +#include "nsITreeSelection.h" +#include "nsStubMutationObserver.h" +#include "nsIDOMNode.h" +#include "nsIDOMDocument.h" +#include "nsTArray.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" + +class inDOMViewNode; +class nsIDOMMozNamedAttrMap; + +class inDOMView : public inIDOMView, + public nsITreeView, + public nsStubMutationObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_INIDOMVIEW + NS_DECL_NSITREEVIEW + + inDOMView(); + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + +protected: + virtual ~inDOMView(); + + nsCOMPtr<nsITreeBoxObject> mTree; + nsCOMPtr<nsITreeSelection> mSelection; + nsCOMPtr<inIDOMUtils> mDOMUtils; + + bool mShowAnonymous; + bool mShowSubDocuments; + bool mShowWhitespaceNodes; + bool mShowAccessibleNodes; + uint32_t mWhatToShow; + + nsCOMPtr<nsIDOMNode> mRootNode; + nsCOMPtr<nsIDOMDocument> mRootDocument; + + nsTArray<inDOMViewNode*> mNodes; + + inDOMViewNode* GetNodeAt(int32_t aIndex); + int32_t GetRowCount(); + int32_t NodeToRow(inDOMViewNode* aNode); + bool RowOutOfBounds(int32_t aRow, int32_t aCount); + inDOMViewNode* CreateNode(nsIDOMNode* aNode, inDOMViewNode* aParent); + void AppendNode(inDOMViewNode* aNode); + void InsertNode(inDOMViewNode* aNode, int32_t aIndex); + void RemoveNode(int32_t aIndex); + void ReplaceNode(inDOMViewNode* aNode, int32_t aIndex); + void InsertNodes(nsTArray<inDOMViewNode*>& aNodes, int32_t aIndex); + void RemoveNodes(int32_t aIndex, int32_t aCount); + void RemoveAllNodes(); + void ExpandNode(int32_t aRow); + void CollapseNode(int32_t aRow); + + nsresult RowToNode(int32_t aRow, inDOMViewNode** aNode); + nsresult NodeToRow(nsIDOMNode* aNode, int32_t* aRow); + + void InsertLinkAfter(inDOMViewNode* aNode, inDOMViewNode* aInsertAfter); + void InsertLinkBefore(inDOMViewNode* aNode, inDOMViewNode* aInsertBefore); + void RemoveLink(inDOMViewNode* aNode); + void ReplaceLink(inDOMViewNode* aNewNode, inDOMViewNode* aOldNode); + + nsresult GetChildNodesFor(nsIDOMNode* aNode, nsCOMArray<nsIDOMNode>& aResult); + nsresult AppendKidsToArray(nsIDOMNodeList* aKids, nsCOMArray<nsIDOMNode>& aArray); + nsresult AppendAttrsToArray(nsIDOMMozNamedAttrMap* aKids, nsCOMArray<nsIDOMNode>& aArray); + nsresult GetFirstDescendantOf(inDOMViewNode* aNode, int32_t aRow, int32_t* aResult); + nsresult GetLastDescendantOf(inDOMViewNode* aNode, int32_t aRow, int32_t* aResult); + nsresult GetRealPreviousSibling(nsIDOMNode* aNode, nsIDOMNode* aRealParent, nsIDOMNode** aSibling); +}; + +// {FB5C1775-1BBD-4b9c-ABB0-AE7ACD29E87E} +#define IN_DOMVIEW_CID \ +{ 0xfb5c1775, 0x1bbd, 0x4b9c, { 0xab, 0xb0, 0xae, 0x7a, 0xcd, 0x29, 0xe8, 0x7e } } + +#endif // __inDOMView_h__ + + diff --git a/layout/inspector/inDeepTreeWalker.cpp b/layout/inspector/inDeepTreeWalker.cpp new file mode 100644 index 000000000..a0c504980 --- /dev/null +++ b/layout/inspector/inDeepTreeWalker.cpp @@ -0,0 +1,442 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=79: */ +/* 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 "nsString.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNodeFilter.h" +#include "nsIDOMNodeList.h" +#include "nsServiceManagerUtils.h" +#include "inIDOMUtils.h" +#include "nsIContent.h" +#include "nsContentList.h" +#include "ChildIterator.h" +#include "mozilla/dom/Element.h" + +/***************************************************************************** + * 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() + : mShowAnonymousContent(false), + mShowSubDocuments(false), + mShowDocumentsAsNodes(false), + mWhatToShow(nsIDOMNodeFilter::SHOW_ALL) +{ +} + +inDeepTreeWalker::~inDeepTreeWalker() +{ +} + +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(nsIDOMNode* aRoot, uint32_t aWhatToShow) +{ + if (!aRoot) { + return NS_ERROR_INVALID_ARG; + } + + mRoot = aRoot; + mCurrentNode = aRoot; + mWhatToShow = aWhatToShow; + + mDOMUtils = do_GetService("@mozilla.org/inspector/dom-utils;1"); + return mDOMUtils ? NS_OK : NS_ERROR_UNEXPECTED; +} + +//////////////////////////////////////////////////// +// nsIDOMTreeWalker + +NS_IMETHODIMP +inDeepTreeWalker::GetRoot(nsIDOMNode** aRoot) +{ + *aRoot = mRoot; + NS_IF_ADDREF(*aRoot); + return NS_OK; +} + +NS_IMETHODIMP +inDeepTreeWalker::GetWhatToShow(uint32_t* aWhatToShow) +{ + *aWhatToShow = mWhatToShow; + return NS_OK; +} + +NS_IMETHODIMP +inDeepTreeWalker::GetFilter(nsIDOMNodeFilter** aFilter) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +inDeepTreeWalker::GetCurrentNode(nsIDOMNode** aCurrentNode) +{ + *aCurrentNode = mCurrentNode; + NS_IF_ADDREF(*aCurrentNode); + return NS_OK; +} + +already_AddRefed<nsIDOMNode> +inDeepTreeWalker::GetParent() +{ + if (mCurrentNode == mRoot) { + return nullptr; + } + + nsCOMPtr<nsIDOMNode> parent; + MOZ_ASSERT(mDOMUtils, "mDOMUtils should have been initiated already in Init"); + mDOMUtils->GetParentForNode(mCurrentNode, mShowAnonymousContent, + getter_AddRefs(parent)); + + uint16_t nodeType = 0; + if (parent) { + parent->GetNodeType(&nodeType); + } + // For compatibility reasons by default we skip the document nodes + // from the walk. + if (!mShowDocumentsAsNodes && + nodeType == nsIDOMNode::DOCUMENT_NODE && + parent != mRoot) { + mDOMUtils->GetParentForNode(parent, mShowAnonymousContent, + getter_AddRefs(parent)); + } + + return parent.forget(); +} + +static already_AddRefed<nsINodeList> +GetChildren(nsIDOMNode* aParent, + bool aShowAnonymousContent, + bool aShowSubDocuments) +{ + MOZ_ASSERT(aParent); + + nsCOMPtr<nsINodeList> ret; + if (aShowSubDocuments) { + nsCOMPtr<nsIDOMDocument> domdoc = inLayoutUtils::GetSubDocumentFor(aParent); + if (domdoc) { + aParent = domdoc; + } + } + + nsCOMPtr<nsIContent> parentAsContent = do_QueryInterface(aParent); + if (parentAsContent && aShowAnonymousContent) { + ret = parentAsContent->GetChildren(nsIContent::eAllChildren); + } else { + // If it's not a content, then it's a document (or an attribute but we can ignore that + // case here). If aShowAnonymousContent is false we also want to fall back to ChildNodes + // so we can skip any native anon content that GetChildren would return. + nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParent); + MOZ_ASSERT(parentNode); + ret = parentNode->ChildNodes(); + } + return ret.forget(); +} + +NS_IMETHODIMP +inDeepTreeWalker::SetCurrentNode(nsIDOMNode* 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. + uint16_t nodeType = 0; + aCurrentNode->GetNodeType(&nodeType); + if (!mShowDocumentsAsNodes && nodeType == nsIDOMNode::DOCUMENT_NODE) { + return NS_ERROR_FAILURE; + } + + return SetCurrentNode(aCurrentNode, nullptr); +} + + +nsresult +inDeepTreeWalker::SetCurrentNode(nsIDOMNode* aCurrentNode, + nsINodeList* aSiblings) +{ + MOZ_ASSERT(aCurrentNode); + + // We want to store the original state so in case of error + // we can restore that. + nsCOMPtr<nsINodeList> tmpSiblings = mSiblings; + nsCOMPtr<nsIDOMNode> tmpCurrent = mCurrentNode; + mSiblings = aSiblings; + mCurrentNode = aCurrentNode; + + // If siblings were not passed in as argument we have to + // get them from the parent node of aCurrentNode. + // Note: in the mShowDoucmentsAsNodes case when a sub document + // is set as the current, we don't want to get the children + // from the iframe accidentally here, so let's just skip this + // part for document nodes, they should never have siblings. + uint16_t nodeType = 0; + aCurrentNode->GetNodeType(&nodeType); + if (!mSiblings && nodeType != nsIDOMNode::DOCUMENT_NODE) { + nsCOMPtr<nsIDOMNode> parent = GetParent(); + if (parent) { + mSiblings = GetChildren(parent, + mShowAnonymousContent, + mShowSubDocuments); + } + } + + if (mSiblings && mSiblings->Length()) { + // 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. + nsCOMPtr<nsIContent> currentAsContent = do_QueryInterface(mCurrentNode); + MOZ_ASSERT(currentAsContent); + int32_t index = mSiblings->IndexOf(currentAsContent); + 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 + // XBL anon content was passed in) + + // Restore state first. + mCurrentNode = tmpCurrent; + mSiblings = tmpSiblings; + return NS_ERROR_INVALID_ARG; + } + mCurrentIndex = index; + } else { + mCurrentIndex = -1; + } + return NS_OK; +} + +NS_IMETHODIMP +inDeepTreeWalker::ParentNode(nsIDOMNode** _retval) +{ + *_retval = nullptr; + if (!mCurrentNode || mCurrentNode == mRoot) { + return NS_OK; + } + + nsCOMPtr<nsIDOMNode> parent = GetParent(); + + if (!parent) { + return NS_OK; + } + + nsresult rv = SetCurrentNode(parent); + NS_ENSURE_SUCCESS(rv,rv); + + 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(nsIDOMNode** _retval, bool aFront) +{ + if (!mCurrentNode) { + return NS_ERROR_FAILURE; + } + + *_retval = nullptr; + + nsCOMPtr<nsIDOMNode> echild; + if (mShowSubDocuments && mShowDocumentsAsNodes) { + // GetChildren below, will skip the document node from + // the walk. But if mShowDocumentsAsNodes is set to true + // we want to include the (sub)document itself too. + echild = inLayoutUtils::GetSubDocumentFor(mCurrentNode); + } + + nsCOMPtr<nsINodeList> children; + if (!echild) { + children = GetChildren(mCurrentNode, + mShowAnonymousContent, + mShowSubDocuments); + if (children && children->Length() > 0) { + nsINode* childNode = children->Item(aFront ? 0 : children->Length() - 1); + echild = childNode ? childNode->AsDOMNode() : nullptr; + } + } + + if (echild) { + nsresult rv = SetCurrentNode(echild, children); + NS_ENSURE_SUCCESS(rv, rv); + NS_ADDREF(*_retval = mCurrentNode); + } + + return NS_OK; +} + +NS_IMETHODIMP +inDeepTreeWalker::FirstChild(nsIDOMNode** _retval) +{ + return EdgeChild(_retval, /* aFront = */ true); +} + +NS_IMETHODIMP +inDeepTreeWalker::LastChild(nsIDOMNode **_retval) +{ + return EdgeChild(_retval, /* aFront = */ false); +} + +NS_IMETHODIMP +inDeepTreeWalker::PreviousSibling(nsIDOMNode **_retval) +{ + *_retval = nullptr; + if (!mCurrentNode || !mSiblings || mCurrentIndex < 1) { + return NS_OK; + } + + nsIContent* prev = mSiblings->Item(--mCurrentIndex); + mCurrentNode = prev->AsDOMNode(); + NS_ADDREF(*_retval = mCurrentNode); + return NS_OK; +} + +NS_IMETHODIMP +inDeepTreeWalker::NextSibling(nsIDOMNode **_retval) +{ + *_retval = nullptr; + if (!mCurrentNode || !mSiblings || + mCurrentIndex + 1 >= (int32_t) mSiblings->Length()) { + return NS_OK; + } + + nsIContent* next = mSiblings->Item(++mCurrentIndex); + mCurrentNode = next->AsDOMNode(); + NS_ADDREF(*_retval = mCurrentNode); + return NS_OK; +} + +NS_IMETHODIMP +inDeepTreeWalker::PreviousNode(nsIDOMNode **_retval) +{ + if (!mCurrentNode || mCurrentNode == mRoot) { + // Nowhere to go from here + *_retval = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIDOMNode> 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(nsIDOMNode **_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 + nsIDOMNode* origCurrentNode = mCurrentNode; +#endif + uint32_t lastChildCallsToMake = 0; + while (1) { + NextSibling(_retval); + + if (*_retval) { + return NS_OK; + } + + nsCOMPtr<nsIDOMNode> parent; + ParentNode(getter_AddRefs(parent)); + if (!parent) { + // Nowhere else to go; we're done. Restore our state. + while (lastChildCallsToMake--) { + nsCOMPtr<nsIDOMNode> dummy; + LastChild(getter_AddRefs(dummy)); + } + NS_ASSERTION(mCurrentNode == origCurrentNode, + "Didn't go back to the right node?"); + *_retval = nullptr; + return NS_OK; + } + ++lastChildCallsToMake; + } + + NS_NOTREACHED("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 000000000..652722808 --- /dev/null +++ b/layout/inspector/inDeepTreeWalker.h @@ -0,0 +1,65 @@ +/* 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 "nsIDOMNode.h" +#include "nsTArray.h" + +class nsINodeList; +class inIDOMUtils; + +class inDeepTreeWalker final : public inIDeepTreeWalker +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_INIDEEPTREEWALKER + + inDeepTreeWalker(); + + nsresult SetCurrentNode(nsIDOMNode* aCurrentNode, + nsINodeList* aSiblings); +protected: + virtual ~inDeepTreeWalker(); + + already_AddRefed<nsIDOMNode> GetParent(); + nsresult EdgeChild(nsIDOMNode** _retval, bool aReverse); + + bool mShowAnonymousContent; + bool mShowSubDocuments; + bool mShowDocumentsAsNodes; + + // The root node. previousNode and parentNode will return + // null from here. + nsCOMPtr<nsIDOMNode> mRoot; + nsCOMPtr<nsIDOMNode> mCurrentNode; + nsCOMPtr<inIDOMUtils> mDOMUtils; + + // 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 + // But if mCurrentNode is a (sub) document then instead of + // storing a list that has only one element (the document) + // and setting mCurrentIndex to null, we set mSibilings to null. + // The reason for this is purely technical, since nsINodeList is + // nsIContent based hence we cannot use it to store a document node. + nsCOMPtr<nsINodeList> mSiblings; + + // Index of mCurrentNode in the mSiblings list. + int32_t mCurrentIndex; + + // Currently unused. Should be a filter for nodes. + uint32_t mWhatToShow; +}; + +// {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/inICSSValueSearch.idl b/layout/inspector/inICSSValueSearch.idl new file mode 100644 index 000000000..a67d72b08 --- /dev/null +++ b/layout/inspector/inICSSValueSearch.idl @@ -0,0 +1,29 @@ +/* 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 "inISearchProcess.idl" + +interface nsIDOMDocument; + +[scriptable, uuid(e0d39e48-1dd1-11b2-81bd-9a0c117f0736)] +interface inICSSValueSearch : inISearchProcess { + + attribute nsIDOMDocument document; + + // the base url for all returned URL results, if returnRelativeURLs is true + attribute wstring baseURL; + + // strip off the baseURL for all URL results if true + attribute boolean returnRelativeURLs; + + // correct the paths on a chrome url, such as turning global/skin/blah into global/blah + attribute boolean normalizeChromeURLs; + + // add a css property to search for + void addPropertyCriteria(in wstring aPropName); + + // set the text value to search for in the properties specified (optional) + attribute wstring textCriteria; + +}; diff --git a/layout/inspector/inIDOMUtils.idl b/layout/inspector/inIDOMUtils.idl new file mode 100644 index 000000000..30c15003f --- /dev/null +++ b/layout/inspector/inIDOMUtils.idl @@ -0,0 +1,213 @@ +/* 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" + +interface nsIArray; +interface nsIArrayExtensions; +interface nsIDOMCharacterData; +interface nsIDOMElement; +interface nsIDOMDocument; +interface nsIDOMCSSRule; +interface nsIDOMCSSStyleRule; +interface nsIDOMNode; +interface nsIDOMNodeList; +interface nsIDOMFontFaceList; +interface nsIDOMRange; +interface nsIDOMCSSStyleSheet; + +[scriptable, uuid(362e98c3-82c2-4ad8-8dcb-00e8e4eab497)] +interface inIDOMUtils : nsISupports +{ + // CSS utilities + void getAllStyleSheets (in nsIDOMDocument aDoc, + [optional] out unsigned long aLength, + [array, size_is (aLength), retval] out nsISupports aSheets); + nsIArrayExtensions getCSSStyleRules(in nsIDOMElement aElement, [optional] in DOMString aPseudo); + + /** + * Get the line number of a rule. + * + * @param nsIDOMCSSRule aRule The rule. + * @return The rule's line number. Line numbers are 1-based. + */ + unsigned long getRuleLine(in nsIDOMCSSRule aRule); + + /** + * Get the column number of a rule. + * + * @param nsIDOMCSSRule aRule The rule. + * @return The rule's column number. Column numbers are 1-based. + */ + unsigned long getRuleColumn(in nsIDOMCSSRule aRule); + + /** + * Like getRuleLine, but if the rule is in a <style> element, + * returns a line number relative to the start of the element. + * + * @param nsIDOMCSSRule aRule the rule to examine + * @return the line number of the rule, possibly relative to the + * <style> element + */ + unsigned long getRelativeRuleLine(in nsIDOMCSSRule aRule); + + [implicit_jscontext] + jsval getCSSLexer(in DOMString aText); + + // Utilities for working with selectors. We don't have a JS OM representation + // of a single selector or a selector list yet, but given a rule we can index + // into the selector list. + // + // This is a somewhat backwards API; once we move StyleRule to WebIDL we + // should consider using [ChromeOnly] APIs on that. + unsigned long getSelectorCount(in nsIDOMCSSStyleRule aRule); + // For all three functions below, aSelectorIndex is 0-based + AString getSelectorText(in nsIDOMCSSStyleRule aRule, + in unsigned long aSelectorIndex); + unsigned long long getSpecificity(in nsIDOMCSSStyleRule aRule, + in unsigned long aSelectorIndex); + // Note: This does not handle scoped selectors correctly, because it has no + // idea what the right scope is. + bool selectorMatchesElement(in nsIDOMElement aElement, + in nsIDOMCSSStyleRule aRule, + in unsigned long aSelectorIndex, + [optional] in DOMString aPseudo); + + // Utilities for working with CSS properties + // + // Returns true if the string names a property that is inherited by default. + bool isInheritedProperty(in AString aPropertyName); + + // Get a list of all our supported property names. Optionally + // shorthands can be excluded or property aliases included. + const unsigned long EXCLUDE_SHORTHANDS = (1<<0); + const unsigned long INCLUDE_ALIASES = (1<<1); + void getCSSPropertyNames([optional] in unsigned long aFlags, + [optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out wstring aProps); + + // Get a list of all valid keywords and colors for aProperty. + void getCSSValuesForProperty(in AString aProperty, + [optional] out unsigned long aLength, + [array, size_is(aLength), retval] out wstring aValues); + + // Utilities for working with CSS colors + [implicit_jscontext] + jsval colorNameToRGB(in DOMString aColorName); + AString rgbToColorName(in octet aR, in octet aG, in octet aB); + + // 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. + [implicit_jscontext] + jsval colorToRGBA(in DOMString aColorString); + + // Check whether a given color is a valid CSS color. + bool isValidCSSColor(in AString aColorString); + + // Utilities for obtaining information about a CSS property. + + // Check whether a CSS property and value are a valid combination. If the + // property is pref-disabled it will still be processed. + // aPropertyName: A property name e.g. "color" + // aPropertyValue: A property value e.g. "red" or "red !important" + bool cssPropertyIsValid(in AString aPropertyName, in AString aPropertyValue); + + // 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. + void getSubpropertiesForCSSProperty(in AString aProperty, + [optional] out unsigned long aLength, + [array, size_is(aLength), retval] out wstring aValues); + // Check whether a given CSS property is a shorthand. Throws on unsupported + // property names. + bool cssPropertyIsShorthand(in AString aProperty); + + // 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. + const unsigned long TYPE_LENGTH = 0; + const unsigned long TYPE_PERCENTAGE = 1; + const unsigned long TYPE_COLOR = 2; + const unsigned long TYPE_URL = 3; + const unsigned long TYPE_ANGLE = 4; + const unsigned long TYPE_FREQUENCY = 5; + const unsigned long TYPE_TIME = 6; + const unsigned long TYPE_GRADIENT = 7; + const unsigned long TYPE_TIMING_FUNCTION = 8; + const unsigned long TYPE_IMAGE_RECT = 9; + const unsigned long TYPE_NUMBER = 10; + bool cssPropertySupportsType(in AString aProperty, in unsigned long type); + + // DOM Node utilities + boolean isIgnorableWhitespace(in nsIDOMCharacterData 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. + nsIDOMNode getParentForNode(in nsIDOMNode aNode, + in boolean aShowingAnonymousContent); + nsIDOMNodeList getChildrenForNode(in nsIDOMNode aNode, + in boolean aShowingAnonymousContent); + + // XBL utilities + nsIArray getBindingURLs(in nsIDOMElement aElement); + + // content state utilities + unsigned long long getContentState(in nsIDOMElement aElement); + /** + * Setting and removing content state on an element. Both these functions + * calling EventStateManager::SetContentState internally, the difference is + * that for the remove case we simply pass in nullptr for the element. + * Use them accordingly. + * + * @return Returns true if the state was set successfully. See more details + * in EventStateManager.h SetContentState. + */ + bool setContentState(in nsIDOMElement aElement, in unsigned long long aState); + bool removeContentState(in nsIDOMElement aElement, in unsigned long long aState); + + nsIDOMFontFaceList getUsedFontFaces(in nsIDOMRange aRange); + + /** + * Get the names of all the supported pseudo-elements. + * Pseudo-elements which are only accepted in UA style sheets are + * not included. + * + * @param {unsigned long} aCount the number of items returned + * @param {wstring[]} aNames the names + */ + void getCSSPseudoElementNames([optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out wstring aNames); + + // 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. + void addPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass); + void removePseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass); + bool hasPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass); + void clearPseudoClassLocks(in nsIDOMElement aElement); + + /** + * Parse CSS and update the style sheet in place. + * + * @param DOMCSSStyleSheet aSheet + * @param DOMString aInput + * The new source string for the style sheet. + */ + void parseStyleSheet(in nsIDOMCSSStyleSheet aSheet, in DOMString aInput); + /** + * Scroll an element completely into view, if possible. + * This is similar to ensureElementIsVisible but for all ancestors. + * + * @param DOMElement aElement + */ + void scrollElementIntoView(in nsIDOMElement aElement); +}; + +%{ C++ +#define IN_DOMUTILS_CONTRACTID "@mozilla.org/inspector/dom-utils;1" +%} diff --git a/layout/inspector/inIDOMView.idl b/layout/inspector/inIDOMView.idl new file mode 100644 index 000000000..da8c543b1 --- /dev/null +++ b/layout/inspector/inIDOMView.idl @@ -0,0 +1,24 @@ +/* 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" + +interface nsIDOMNode; + +[scriptable, uuid(FBB67442-27A3-483C-8EB2-29C3EED7514C)] +interface inIDOMView : nsISupports +{ + attribute nsIDOMNode rootNode; + + attribute boolean showAnonymousContent; + attribute boolean showSubDocuments; + attribute boolean showWhitespaceNodes; + attribute boolean showAccessibleNodes; + attribute unsigned long whatToShow; + + nsIDOMNode getNodeFromRowIndex(in long rowIndex); + long getRowIndexFromNode(in nsIDOMNode node); + + void rebuild(); +}; diff --git a/layout/inspector/inIDeepTreeWalker.idl b/layout/inspector/inIDeepTreeWalker.idl new file mode 100644 index 000000000..389f3c86e --- /dev/null +++ b/layout/inspector/inIDeepTreeWalker.idl @@ -0,0 +1,47 @@ +/* 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" + +interface nsIDOMNode; +interface nsIDOMNodeFilter; + +// 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 nsIDOMNode aRoot, in unsigned long aWhatToShow); + + // Methods and attributes from nsIDOMTreeWalker, which is not scriptable. + // 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 nsIDOMNode root; + readonly attribute unsigned long whatToShow; + readonly attribute nsIDOMNodeFilter filter; + attribute nsIDOMNode currentNode; + + nsIDOMNode parentNode(); + nsIDOMNode firstChild(); + nsIDOMNode lastChild(); + nsIDOMNode previousSibling(); + nsIDOMNode nextSibling(); + nsIDOMNode previousNode(); + nsIDOMNode nextNode(); +}; + diff --git a/layout/inspector/inISearchObserver.idl b/layout/inspector/inISearchObserver.idl new file mode 100644 index 000000000..60de3246d --- /dev/null +++ b/layout/inspector/inISearchObserver.idl @@ -0,0 +1,21 @@ +/* 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" + +interface inISearchProcess; + +[scriptable, uuid(46226D9B-E398-4106-8D9B-225D4D0589F5)] +interface inISearchObserver : nsISupports +{ + // result codes which are sent to onSearchEnd + const short IN_SUCCESS = 1; // search completed successfully + const short IN_INTERRUPTED = 2; // search stopped due to user interruption + const short IN_ERROR = 3; // search stopped due to an error + + void onSearchStart(in inISearchProcess aModule); + void onSearchResult(in inISearchProcess aModule); + void onSearchEnd(in inISearchProcess aModule, in short aResult); + void onSearchError(in inISearchProcess aModule, in AString aMessage); +}; diff --git a/layout/inspector/inISearchProcess.idl b/layout/inspector/inISearchProcess.idl new file mode 100644 index 000000000..d1e2f1ca2 --- /dev/null +++ b/layout/inspector/inISearchProcess.idl @@ -0,0 +1,49 @@ +/* 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" + +interface nsAString; +interface inISearchObserver; + +[scriptable, uuid(D5FA765B-2448-4686-B7C1-5FF13ACB0FC9)] +interface inISearchProcess : nsISupports +{ + // indicates if an asynchronous search is in progress + readonly attribute boolean isActive; + + // the number of results returned + readonly attribute long resultCount; + + // for optimization when doing an async search, this will optionally + // destroy old results, assuming they will be fetched as soon as + // the observer is notified of their presence. If true, then indices + // pass to the get*ResultAt methods will return null for any index + // other than the most recent one, and getResults will return null always. + attribute boolean holdResults; + + // start a synchronous search + void searchSync(); + + // start an asynchronous search + void searchAsync(in inISearchObserver aObserver); + + // command an async process to stop immediately + void searchStop(); + + // performs a step in the asynchronous search loop + // return indicates true if loop is done, false if it should continue + // This is there only for the benefit of asynchronous search controllers, + // and is not for use by those who just wish to call searchAsync + boolean searchStep(); + + // methods for getting results of specific types + + AString getStringResultAt(in long aIndex); + + long getIntResultAt(in long aIndex); + + unsigned long getUIntResultAt(in long aIndex); +}; diff --git a/layout/inspector/inLayoutUtils.cpp b/layout/inspector/inLayoutUtils.cpp new file mode 100644 index 000000000..eb502f905 --- /dev/null +++ b/layout/inspector/inLayoutUtils.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "inLayoutUtils.h" + +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIContent.h" +#include "nsIContentViewer.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; + +/////////////////////////////////////////////////////////////////////////////// + +EventStateManager* +inLayoutUtils::GetEventStateManagerFor(nsIDOMElement *aElement) +{ + NS_PRECONDITION(aElement, "Passing in a null element is bad"); + + nsCOMPtr<nsIDOMDocument> domDoc; + aElement->GetOwnerDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + + if (!doc) { + NS_WARNING("Could not get an nsIDocument!"); + return nullptr; + } + + nsIPresShell *shell = doc->GetShell(); + if (!shell) + return nullptr; + + return shell->GetPresContext()->EventStateManager(); +} + +nsIDOMDocument* +inLayoutUtils::GetSubDocumentFor(nsIDOMNode* aNode) +{ + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + if (content) { + nsCOMPtr<nsIDocument> doc = content->GetComposedDoc(); + if (doc) { + nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc->GetSubDocumentFor(content))); + + return domdoc; + } + } + + return nullptr; +} + +nsIDOMNode* +inLayoutUtils::GetContainerFor(const nsIDocument& aDoc) +{ + nsPIDOMWindowOuter* pwin = aDoc.GetWindow(); + if (!pwin) { + return nullptr; + } + + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(pwin->GetFrameElementInternal()); + return node; +} + diff --git a/layout/inspector/inLayoutUtils.h b/layout/inspector/inLayoutUtils.h new file mode 100644 index 000000000..e86e7d71c --- /dev/null +++ b/layout/inspector/inLayoutUtils.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 nsIDocument; +class nsIDOMDocument; +class nsIDOMElement; +class nsIDOMNode; + +namespace mozilla { +class EventStateManager; +} // namespace mozilla + +class inLayoutUtils +{ +public: + static mozilla::EventStateManager* + GetEventStateManagerFor(nsIDOMElement *aElement); + static nsIDOMDocument* GetSubDocumentFor(nsIDOMNode* aNode); + static nsIDOMNode* GetContainerFor(const nsIDocument& aDoc); +}; + +#endif // __inLayoutUtils_h__ diff --git a/layout/inspector/inSearchLoop.cpp b/layout/inspector/inSearchLoop.cpp new file mode 100644 index 000000000..a2e46b932 --- /dev/null +++ b/layout/inspector/inSearchLoop.cpp @@ -0,0 +1,58 @@ +/* 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 "inSearchLoop.h" + +#include "nsITimer.h" +#include "nsIServiceManager.h" +/////////////////////////////////////////////////////////////////////////////// + +inSearchLoop::inSearchLoop(inISearchProcess* aSearchProcess) +{ + mSearchProcess = aSearchProcess; + nsresult rv; + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); +} + +inSearchLoop::~inSearchLoop() +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// inSearchLoop + +nsresult +inSearchLoop::Start() +{ + mTimer->InitWithFuncCallback(inSearchLoop::TimerCallback, (void*)this, 0, nsITimer::TYPE_REPEATING_SLACK); + + return NS_OK; +} + +nsresult +inSearchLoop::Step() +{ + bool done = false; + mSearchProcess->SearchStep(&done); + + if (done) + Stop(); + + return NS_OK; +} + +nsresult +inSearchLoop::Stop() +{ + mTimer->Cancel(); + + return NS_OK; +} + +void +inSearchLoop::TimerCallback(nsITimer *aTimer, void *aClosure) +{ + inSearchLoop* loop = (inSearchLoop*) aClosure; + loop->Step(); +} diff --git a/layout/inspector/inSearchLoop.h b/layout/inspector/inSearchLoop.h new file mode 100644 index 000000000..657ee4392 --- /dev/null +++ b/layout/inspector/inSearchLoop.h @@ -0,0 +1,28 @@ +/* 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 __inSearchLoop_h__ +#define __inSearchLoop_h__ + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "inISearchProcess.h" + +class inSearchLoop +{ +public: + explicit inSearchLoop(inISearchProcess* aSearchProcess); + virtual ~inSearchLoop(); + + nsresult Start(); + nsresult Step(); + nsresult Stop(); + static void TimerCallback(nsITimer *aTimer, void *aClosure); + +protected: + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<inISearchProcess> mSearchProcess; +}; + +#endif diff --git a/layout/inspector/moz.build b/layout/inspector/moz.build new file mode 100644 index 000000000..1576b7d14 --- /dev/null +++ b/layout/inspector/moz.build @@ -0,0 +1,49 @@ +# -*- 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.ini'] + MOCHITEST_MANIFESTS += ['tests/mochitest.ini'] + +XPIDL_SOURCES += [ + 'inICSSValueSearch.idl', + 'inIDeepTreeWalker.idl', + 'inIDOMUtils.idl', + 'inIDOMView.idl', + 'inISearchObserver.idl', + 'inISearchProcess.idl', + 'nsIDOMFontFace.idl', + 'nsIDOMFontFaceList.idl', +] + +XPIDL_MODULE = 'inspector' + +EXPORTS += [ + 'nsFontFace.h', + 'nsFontFaceList.h', +] + +UNIFIED_SOURCES += [ + 'inCSSValueSearch.cpp', + 'inDeepTreeWalker.cpp', + 'inDOMUtils.cpp', + 'inLayoutUtils.cpp', + 'inSearchLoop.cpp', + 'nsFontFace.cpp', + 'nsFontFaceList.cpp', +] + +if CONFIG['MOZ_XUL']: + UNIFIED_SOURCES += [ + 'inDOMView.cpp', + ] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../style', + '/dom/base', + '/dom/xbl', +] diff --git a/layout/inspector/nsFontFace.cpp b/layout/inspector/nsFontFace.cpp new file mode 100644 index 000000000..a4a9231e2 --- /dev/null +++ b/layout/inspector/nsFontFace.cpp @@ -0,0 +1,225 @@ +/* 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 "nsFontFace.h" +#include "nsIDOMCSSFontFaceRule.h" +#include "nsCSSRules.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" +#include "nsFontFaceLoader.h" +#include "mozilla/gfx/2D.h" +#include "decode.h" +#include "zlib.h" +#include "mozilla/dom/FontFaceSet.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsFontFace::nsFontFace(gfxFontEntry* aFontEntry, + gfxFontGroup* aFontGroup, + uint8_t aMatchType) + : mFontEntry(aFontEntry), + mFontGroup(aFontGroup), + mMatchType(aMatchType) +{ +} + +nsFontFace::~nsFontFace() +{ +} + +//////////////////////////////////////////////////////////////////////// +// nsISupports + +NS_IMPL_ISUPPORTS(nsFontFace, nsIDOMFontFace) + +//////////////////////////////////////////////////////////////////////// +// nsIDOMFontFace + +NS_IMETHODIMP +nsFontFace::GetFromFontGroup(bool * aFromFontGroup) +{ + *aFromFontGroup = + (mMatchType & gfxTextRange::kFontGroup) != 0; + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetFromLanguagePrefs(bool * aFromLanguagePrefs) +{ + *aFromLanguagePrefs = + (mMatchType & gfxTextRange::kPrefsFallback) != 0; + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetFromSystemFallback(bool * aFromSystemFallback) +{ + *aFromSystemFallback = + (mMatchType & gfxTextRange::kSystemFallback) != 0; + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetName(nsAString & aName) +{ + if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) { + NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData"); + aName = mFontEntry->mUserFontData->mRealName; + } else { + aName = mFontEntry->RealFaceName(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetCSSFamilyName(nsAString & aCSSFamilyName) +{ + aCSSFamilyName = mFontEntry->FamilyName(); + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetRule(nsIDOMCSSFontFaceRule **aRule) +{ + // check whether this font entry is associated with an @font-face rule + // in the relevant font group's user font set + nsCSSFontFaceRule* rule = nullptr; + if (mFontEntry->IsUserFont()) { + FontFaceSet::UserFontSet* fontSet = + static_cast<FontFaceSet::UserFontSet*>(mFontGroup->GetUserFontSet()); + if (fontSet) { + FontFaceSet* fontFaceSet = fontSet->GetFontFaceSet(); + if (fontFaceSet) { + rule = fontFaceSet->FindRuleForEntry(mFontEntry); + } + } + } + + NS_IF_ADDREF(*aRule = rule); + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetSrcIndex(int32_t * aSrcIndex) +{ + if (mFontEntry->IsUserFont()) { + NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData"); + *aSrcIndex = mFontEntry->mUserFontData->mSrcIndex; + } else { + *aSrcIndex = -1; + } + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetURI(nsAString & aURI) +{ + aURI.Truncate(); + if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) { + NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData"); + if (mFontEntry->mUserFontData->mURI) { + nsAutoCString spec; + nsresult rv = mFontEntry->mUserFontData->mURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + AppendUTF8toUTF16(spec, aURI); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::GetLocalName(nsAString & aLocalName) +{ + if (mFontEntry->IsLocalUserFont()) { + NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData"); + aLocalName = mFontEntry->mUserFontData->mLocalName; + } else { + aLocalName.Truncate(); + } + return NS_OK; +} + +static void +AppendToFormat(nsAString & aResult, const char* aFormat) +{ + if (!aResult.IsEmpty()) { + aResult.Append(','); + } + aResult.AppendASCII(aFormat); +} + +NS_IMETHODIMP +nsFontFace::GetFormat(nsAString & aFormat) +{ + aFormat.Truncate(); + if (mFontEntry->IsUserFont() && !mFontEntry->IsLocalUserFont()) { + NS_ASSERTION(mFontEntry->mUserFontData, "missing userFontData"); + uint32_t formatFlags = mFontEntry->mUserFontData->mFormat; + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_OPENTYPE) { + AppendToFormat(aFormat, "opentype"); + } + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_TRUETYPE) { + AppendToFormat(aFormat, "truetype"); + } + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT) { + AppendToFormat(aFormat, "truetype-aat"); + } + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_EOT) { + AppendToFormat(aFormat, "embedded-opentype"); + } + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_SVG) { + AppendToFormat(aFormat, "svg"); + } + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_WOFF) { + AppendToFormat(aFormat, "woff"); + } + if (formatFlags & gfxUserFontSet::FLAG_FORMAT_WOFF2) { + AppendToFormat(aFormat, "woff2"); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsFontFace::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 (BrotliDecompressBuffer(userFontData->mMetadata.Length(), + userFontData->mMetadata.Elements(), + &decodedSize, + (uint8_t*)str.BeginWriting()) == 1 && + decodedSize == userFontData->mMetaOrigLen) { + AppendUTF8toUTF16(str, aMetadata); + } + } + break; + } + } + } + } + return NS_OK; +} diff --git a/layout/inspector/nsFontFace.h b/layout/inspector/nsFontFace.h new file mode 100644 index 000000000..911df04c9 --- /dev/null +++ b/layout/inspector/nsFontFace.h @@ -0,0 +1,37 @@ +/* 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 __nsFontFace_h__ +#define __nsFontFace_h__ + +#include "nsIDOMFontFace.h" + +class gfxFontEntry; +class gfxFontGroup; + +class nsFontFace : public nsIDOMFontFace +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMFONTFACE + + nsFontFace(gfxFontEntry* aFontEntry, + gfxFontGroup* aFontGroup, + uint8_t aMatchInfo); + + gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); } + + void AddMatchType(uint8_t aMatchType) { + mMatchType |= aMatchType; + } + +protected: + virtual ~nsFontFace(); + + RefPtr<gfxFontEntry> mFontEntry; + RefPtr<gfxFontGroup> mFontGroup; + uint8_t mMatchType; +}; + +#endif // __nsFontFace_h__ diff --git a/layout/inspector/nsFontFaceList.cpp b/layout/inspector/nsFontFaceList.cpp new file mode 100644 index 000000000..5f9b0ecb4 --- /dev/null +++ b/layout/inspector/nsFontFaceList.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "nsFontFaceList.h" +#include "nsFontFace.h" +#include "nsFontFaceLoader.h" +#include "nsIFrame.h" +#include "gfxTextRun.h" +#include "mozilla/gfx/2D.h" + +nsFontFaceList::nsFontFaceList() +{ +} + +nsFontFaceList::~nsFontFaceList() +{ +} + +//////////////////////////////////////////////////////////////////////// +// nsISupports + +NS_IMPL_ISUPPORTS(nsFontFaceList, nsIDOMFontFaceList) + +//////////////////////////////////////////////////////////////////////// +// nsIDOMFontFaceList + +NS_IMETHODIMP +nsFontFaceList::Item(uint32_t index, nsIDOMFontFace **_retval) +{ + NS_ENSURE_TRUE(index < mFontFaces.Count(), NS_ERROR_INVALID_ARG); + + uint32_t current = 0; + nsIDOMFontFace* result = nullptr; + for (auto iter = mFontFaces.Iter(); !iter.Done(); iter.Next()) { + if (current == index) { + result = iter.UserData(); + break; + } + current++; + } + NS_ASSERTION(result != nullptr, "null entry in nsFontFaceList?"); + NS_IF_ADDREF(*_retval = result); + return NS_OK; +} + +NS_IMETHODIMP +nsFontFaceList::GetLength(uint32_t *aLength) +{ + *aLength = mFontFaces.Count(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// nsFontFaceList + +nsresult +nsFontFaceList::AddFontsFromTextRun(gfxTextRun* aTextRun, + uint32_t aOffset, uint32_t aLength) +{ + gfxTextRun::Range range(aOffset, aOffset + aLength); + gfxTextRun::GlyphRunIterator iter(aTextRun, range); + while (iter.NextRun()) { + gfxFontEntry *fe = iter.GetGlyphRun()->mFont->GetFontEntry(); + // if we have already listed this face, just make sure the match type is + // recorded + nsFontFace* existingFace = + static_cast<nsFontFace*>(mFontFaces.GetWeak(fe)); + if (existingFace) { + existingFace->AddMatchType(iter.GetGlyphRun()->mMatchType); + } else { + // A new font entry we haven't seen before + RefPtr<nsFontFace> ff = + new nsFontFace(fe, aTextRun->GetFontGroup(), + iter.GetGlyphRun()->mMatchType); + mFontFaces.Put(fe, ff); + } + } + + return NS_OK; +} diff --git a/layout/inspector/nsFontFaceList.h b/layout/inspector/nsFontFaceList.h new file mode 100644 index 000000000..a17c4fc40 --- /dev/null +++ b/layout/inspector/nsFontFaceList.h @@ -0,0 +1,34 @@ +/* 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 __nsFontFaceList_h__ +#define __nsFontFaceList_h__ + +#include "nsIDOMFontFaceList.h" +#include "nsIDOMFontFace.h" +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" + +class gfxFontEntry; +class gfxTextRun; + +class nsFontFaceList : public nsIDOMFontFaceList +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMFONTFACELIST + + nsFontFaceList(); + + nsresult AddFontsFromTextRun(gfxTextRun* aTextRun, + uint32_t aOffset, uint32_t aLength); + +protected: + virtual ~nsFontFaceList(); + + nsInterfaceHashtable<nsPtrHashKey<gfxFontEntry>,nsIDOMFontFace> mFontFaces; +}; + +#endif // __nsFontFaceList_h__ diff --git a/layout/inspector/nsIDOMFontFace.idl b/layout/inspector/nsIDOMFontFace.idl new file mode 100644 index 000000000..39164a7f9 --- /dev/null +++ b/layout/inspector/nsIDOMFontFace.idl @@ -0,0 +1,32 @@ +/* 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" + +interface nsIDOMCSSFontFaceRule; +interface nsIDOMCSSStyleDeclaration; + +[scriptable, uuid(9a3b1272-6585-4f41-b08f-fdc5da444cd0)] +interface nsIDOMFontFace : nsISupports +{ + // An indication of how we found this font during font-matching. + // Note that the same physical font may have been found in multiple ways within a range. + readonly attribute boolean fromFontGroup; + readonly attribute boolean fromLanguagePrefs; + readonly attribute boolean fromSystemFallback; + + // available for all fonts + readonly attribute DOMString name; // full font name as obtained from the font resource + readonly attribute DOMString CSSFamilyName; // a family name that could be used in CSS font-family + // (not necessarily the actual name that was used, + // due to aliases, generics, localized names, etc) + + // meaningful only when the font is a user font defined using @font-face + readonly attribute nsIDOMCSSFontFaceRule rule; // null if no associated @font-face rule + readonly attribute long srcIndex; // index in the rule's src list, -1 if no @font-face rule + readonly attribute DOMString URI; // null if not a downloaded font, i.e. local + readonly attribute DOMString localName; // null if not a src:local(...) rule + readonly attribute DOMString format; // as per http://www.w3.org/TR/css3-webfonts/#referencing + readonly attribute DOMString metadata; // XML metadata from WOFF file (if any) +}; diff --git a/layout/inspector/nsIDOMFontFaceList.idl b/layout/inspector/nsIDOMFontFaceList.idl new file mode 100644 index 000000000..8a565a9b4 --- /dev/null +++ b/layout/inspector/nsIDOMFontFaceList.idl @@ -0,0 +1,14 @@ +/* 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" + +interface nsIDOMFontFace; + +[scriptable, uuid(2538579c-9472-4fd9-8dc1-d44ce4c1b7ba)] +interface nsIDOMFontFaceList : nsISupports +{ + nsIDOMFontFace item(in unsigned long index); + readonly attribute unsigned long length; +}; diff --git a/layout/inspector/tests/bug1202095-2.css b/layout/inspector/tests/bug1202095-2.css new file mode 100644 index 000000000..a484814eb --- /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 000000000..fa8ef0feb --- /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 000000000..49691e3eb --- /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/GentiumPlus-R.woff b/layout/inspector/tests/chrome/GentiumPlus-R.woff Binary files differnew file mode 100644 index 000000000..ebefd081a --- /dev/null +++ b/layout/inspector/tests/chrome/GentiumPlus-R.woff diff --git a/layout/inspector/tests/chrome/chrome.ini b/layout/inspector/tests/chrome/chrome.ini new file mode 100644 index 000000000..ef4b74bda --- /dev/null +++ b/layout/inspector/tests/chrome/chrome.ini @@ -0,0 +1,12 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = GentiumPlus-R.woff + +[test_bug467669.css] +[test_bug467669.xul] +[test_bug695639.css] +[test_bug695639.xul] +[test_bug708874.css] +[test_bug708874.xul] +[test_bug727834.css] +[test_bug727834.xul] diff --git a/layout/inspector/tests/chrome/test_bug467669.css b/layout/inspector/tests/chrome/test_bug467669.css new file mode 100644 index 000000000..fb050bf31 --- /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.xul b/layout/inspector/tests/chrome/test_bug467669.xul new file mode 100644 index 000000000..a5ecd32a7 --- /dev/null +++ b/layout/inspector/tests/chrome/test_bug467669.xul @@ -0,0 +1,174 @@ +<?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 type="application/javascript" 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 CI = Components.interfaces; + const CC = Components.classes; + + const kIsLinux = navigator.platform.indexOf("Linux") == 0; + const kIsMac = navigator.platform.indexOf("Mac") == 0; + const kIsWin = navigator.platform.indexOf("Win") == 0; + + var domUtils = + CC["@mozilla.org/inspector/dom-utils;1"].getService(CI.inIDOMUtils); + + var rng = document.createRange(); + var elem, fonts, f; + + elem = document.getElementById("test1"); + rng.selectNode(elem); + fonts = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts for simple Latin text"); + f = fonts.item(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 = domUtils.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 = domUtils.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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts in @font-face test"); + f = fonts.item(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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts (Latin-only)"); + f = fonts.item(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 = domUtils.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.item(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 ­ 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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts for last char"); + f = fonts.item(0); + is(f.name, "Gentium Plus", "font name"); + + // include the preceding character as well + rng.setStart(elem, elem.length - 2); + fonts = domUtils.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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts for Chinese char"); + f = fonts.item(0); + isnot(f.name, "Gentium Plus", "font name for Chinese char"); + + rng.selectNode(elem); + fonts = domUtils.getUsedFontFaces(rng); +// report("test5", fonts); + + elem = document.getElementById("test6"); + rng.selectNode(elem); + fonts = domUtils.getUsedFontFaces(rng); + is(fonts.length, 2, "number of font faces for regular & italic"); + is(fonts.item(0).CSSFamilyName, fonts.item(1).CSSFamilyName, "same family for regular & italic"); + isnot(fonts.item(0).name, fonts.item(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, 你好</div> + <div id="test4" class="gentium">Hello Gentium Plus!</div> + <div id="test5" class="gentium">supercalifragilistic你ex­pi­a­li­do­cious好!</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 000000000..549537498 --- /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.xul b/layout/inspector/tests/chrome/test_bug695639.xul new file mode 100644 index 000000000..cc2c879c9 --- /dev/null +++ b/layout/inspector/tests/chrome/test_bug695639.xul @@ -0,0 +1,80 @@ +<?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 type="application/javascript" 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() { + const CI = Components.interfaces; + const CC = Components.classes; + + var domUtils = + CC["@mozilla.org/inspector/dom-utils;1"].getService(CI.inIDOMUtils); + + var rng = document.createRange(); + var elem, fonts, f; + + elem = document.getElementById("test").childNodes[0]; + rng.setStart(elem, 0); + rng.setEnd(elem, 14); + fonts = domUtils.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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts (1)"); + f = fonts.item(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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts (2)"); + f = fonts.item(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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts (3)"); + f = fonts.item(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 = domUtils.getUsedFontFaces(rng); + is(fonts.length, 1, "number of fonts (4)"); + f = fonts.item(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 你好 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 000000000..539c44538 --- /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.xul b/layout/inspector/tests/chrome/test_bug708874.xul new file mode 100644 index 000000000..0896d1eca --- /dev/null +++ b/layout/inspector/tests/chrome/test_bug708874.xul @@ -0,0 +1,296 @@ +<?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 type="application/javascript" 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 DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"] + .getService(Components.interfaces.inIDOMUtils); +var DOMWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + +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(DOMUtils.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 */ + DOMUtils.addPseudoClassLock(button, ":disabled"); + + is(DOMUtils.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 */ + DOMUtils.removePseudoClassLock(button, ":disabled"); + + is(DOMUtils.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(DOMUtils.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 */ + DOMUtils.addPseudoClassLock(button, ":disabled"); + + is(DOMUtils.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 */ + DOMUtils.removePseudoClassLock(button, ":disabled"); + + is(DOMUtils.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 */ + DOMUtils.addPseudoClassLock(link, ":visited"); + + is(DOMUtils.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 */ + DOMUtils.addPseudoClassLock(link, ":link"); + + is(DOMUtils.hasPseudoClassLock(link, ":link"), true, + "hasPseudoClassLock is true after adding :link lock"); + + is(DOMUtils.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 */ + DOMUtils.addPseudoClassLock(link, ":visited"); + + is(DOMUtils.hasPseudoClassLock(link, ":visited"), true, + "hasPseudoClassLock is true after adding :visited lock"); + + is(DOMUtils.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) { + DOMUtils.addPseudoClassLock(div, pseudo); + } + + for (var pseudo in styles) { + is(DOMUtils.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"); + } + + DOMUtils.clearPseudoClassLocks(div); + + for (var pseudo in styles) { + is(DOMUtils.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. + DOMUtils.addPseudoClassLock(div, pseudo); + + is(DOMUtils.hasPseudoClassLock(div, pseudo), false); + + DOMUtils.removePseudoClassLock(div, pseudo); + } +} + +function testNotElement() { + for (var value of [null, undefined, {}]) { + SimpleTest.doesThrow(() => DOMUtils.hasPseudoClassLock(value, ":hover"), + "hasPseudoClassLock should throw for " + value); + SimpleTest.doesThrow(() => DOMUtils.addPseudoClassLock(value, ":hover"), + "addPseudoClassLock should throw for " + value); + SimpleTest.doesThrow(() => DOMUtils.removePseudoClassLock(value, ":hover"), + "removePseudoClassLock should throw for " + value); + SimpleTest.doesThrow(() => DOMUtils.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 000000000..f21f7a54c --- /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.xul b/layout/inspector/tests/chrome/test_bug727834.xul new file mode 100644 index 000000000..dde8e1b03 --- /dev/null +++ b/layout/inspector/tests/chrome/test_bug727834.xul @@ -0,0 +1,88 @@ +<?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 type="application/javascript" 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 DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"] + .getService(Components.interfaces.inIDOMUtils); + 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"); + + DOMUtils.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"); + + DOMUtils.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 () { + DOMUtils.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/file_bug522601.html b/layout/inspector/tests/file_bug522601.html new file mode 100644 index 000000000..c3e27a4b5 --- /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 type="application/javascript" 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/mochitest.ini b/layout/inspector/tests/mochitest.ini new file mode 100644 index 000000000..25209f57a --- /dev/null +++ b/layout/inspector/tests/mochitest.ini @@ -0,0 +1,29 @@ +[DEFAULT] +support-files = + bug1202095.css + bug1202095-2.css + bug856317.css + file_bug522601.html + +[test_bug462787.html] +[test_bug462789.html] +[test_bug522601.xhtml] +[test_bug536379.html] +[test_bug536379-2.html] +[test_bug557726.html] +[test_bug609549.xhtml] +[test_bug806192.html] +[test_bug856317.html] +[test_bug877690.html] +[test_bug1006595.html] +[test_color_to_rgba.html] +[test_css_property_is_shorthand.html] +[test_css_property_is_valid.html] +[test_getCSSPseudoElementNames.html] +[test_getRelativeRuleLine.html] +[test_get_all_style_sheets.html] +[test_is_valid_css_color.html] +[test_isinheritableproperty.html] +[test_parseStyleSheet.html] +[test_parseStyleSheetImport.html] +[test_selectormatcheselement.html] diff --git a/layout/inspector/tests/test_bug1006595.html b/layout/inspector/tests/test_bug1006595.html new file mode 100644 index 000000000..009269d37 --- /dev/null +++ b/layout/inspector/tests/test_bug1006595.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1006595 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1006595</title> + <script type="application/javascript" 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 **/ + 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 utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + var paddingSubProps = utils.getSubpropertiesForCSSProperty("padding"); + arraysEqual(paddingSubProps, + [ "padding-top", + "padding-right", + "padding-bottom", + "padding-left" ], + "'padding' subproperties"); + + var displaySubProps = utils.getSubpropertiesForCSSProperty("color"); + arraysEqual(displaySubProps, [ "color" ], + "'color' subproperties"); + + var varProps = utils.getSubpropertiesForCSSProperty("--foo"); + arraysEqual(varProps, ["--foo"], "'--foo' subproperties"); + + ok(utils.cssPropertyIsShorthand("padding"), "'padding' is a shorthand") + ok(!utils.cssPropertyIsShorthand("color"), "'color' is not a shorthand") + + ok(utils.cssPropertySupportsType("padding", utils.TYPE_LENGTH), + "'padding' can be a length"); + ok(!utils.cssPropertySupportsType("padding", utils.TYPE_COLOR), + "'padding' can't be a color"); + + ok(utils.cssPropertySupportsType("padding", utils.TYPE_PERCENTAGE), + "'padding' can be a percentage"); + ok(!utils.cssPropertySupportsType("color", utils.TYPE_PERCENTAGE), + "'color' can't be a percentage"); + + ok(utils.cssPropertySupportsType("color", utils.TYPE_COLOR), + "'color' can be a color"); + ok(utils.cssPropertySupportsType("background", utils.TYPE_COLOR), + "'background' can be a color"); + ok(!utils.cssPropertySupportsType("background-image", utils.TYPE_COLOR), + "'background-image' can't be a color"); + + ok(utils.cssPropertySupportsType("background-image", utils.TYPE_URL), + "'background-image' can be a URL"); + ok(utils.cssPropertySupportsType("background", utils.TYPE_URL), + "'background' can be a URL"); + ok(!utils.cssPropertySupportsType("background-color", utils.TYPE_URL), + "'background-color' can't be a URL"); + + // There are no properties claiming to be of TYPE_ANGLE. image-orientation + // would be, but it doesn't use table-driven parsing. + + // There are no properties claiming to be of TYPE_FREQUENCY + + ok(utils.cssPropertySupportsType("transition", utils.TYPE_TIME), + "'transition' can be a time"); + ok(utils.cssPropertySupportsType("transition-duration", utils.TYPE_TIME), + "'transition-duration' can be a time"); + ok(!utils.cssPropertySupportsType("background-color", utils.TYPE_TIME), + "'background-color' can't be a time"); + + ok(utils.cssPropertySupportsType("background-image", utils.TYPE_GRADIENT), + "'background-image' can be a gradient"); + ok(utils.cssPropertySupportsType("background", utils.TYPE_GRADIENT), + "'background' can be a gradient"); + ok(!utils.cssPropertySupportsType("background-color", utils.TYPE_GRADIENT), + "'background-color' can't be a gradient"); + + ok(utils.cssPropertySupportsType("transition", utils.TYPE_TIMING_FUNCTION), + "'transition' can be a timing function"); + ok(utils.cssPropertySupportsType("transition-timing-function", + utils.TYPE_TIMING_FUNCTION), + "'transition-duration' can be a timing function"); + ok(!utils.cssPropertySupportsType("background-color", + utils.TYPE_TIMING_FUNCTION), + "'background-color' can't be a timing function"); + + ok(utils.cssPropertySupportsType("background-image", utils.TYPE_IMAGE_RECT), + "'background-image' can be an image rect"); + ok(utils.cssPropertySupportsType("background", utils.TYPE_IMAGE_RECT), + "'background' can be an image rect"); + ok(!utils.cssPropertySupportsType("background-color", utils.TYPE_IMAGE_RECT), + "'background-color' can't be an image rect"); + + ok(utils.cssPropertySupportsType("z-index", utils.TYPE_NUMBER), + "'z-index' can be a number"); + ok(utils.cssPropertySupportsType("line-height", utils.TYPE_NUMBER), + "'line-height' can be a number"); + ok(!utils.cssPropertySupportsType("background-color", utils.TYPE_NUMBER), + "'background-color' can't be a number"); + + </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 000000000..88c5c466f --- /dev/null +++ b/layout/inspector/tests/test_bug462787.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462787 +--> +<head> + <title>Test for Bug 462787</title> + <script type="application/javascript" 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 **/ + +function do_test() { + const INVALID_POINTER = SpecialPowers.Cr.NS_ERROR_INVALID_POINTER; + + var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + try { + utils.getCSSStyleRules(null); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + try { + utils.getRuleLine(null); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + try { + utils.isIgnorableWhitespace(null); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + try { + utils.getParentForNode(null, true); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + try { + utils.getBindingURLs(null); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + try { + utils.getContentState(null); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + try { + utils.setContentState(null, false); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, INVALID_POINTER, "got the expected exception"); + } + + 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 000000000..a027b85a6 --- /dev/null +++ b/layout/inspector/tests/test_bug462789.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462789 +--> +<head> + <title>Test for Bug 462789</title> + <script type="application/javascript" 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" src="data:text/html,<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 **/ + +function do_test() { + const ERROR_INVALID_ARG = 0x80070057; + const DOCUMENT_NODE_TYPE = 9; + + var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + 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 = utils.getCSSStyleRules(docElement); + is(res, null, "getCSSStyleRules"); + res = utils.getCSSStyleRules(body); + is(res, null, "getCSSStyleRules"); + } + catch(e) { ok(false, "got an unexpected exception:" + e); } + + try { + var res = utils.getRuleLine(rule); + is(res, 1, "getRuleLine"); + } + catch(e) { ok(false, "got an unexpected exception:" + e); } + + try { + var res = utils.isIgnorableWhitespace(text); + is(res, false, "isIgnorableWhitespace"); + } + catch(e) { ok(false, "got an unexpected exception:" + e); } + + try { + var res = utils.getParentForNode(docElement, true); + is(res.nodeType, DOCUMENT_NODE_TYPE, "getParentForNode(docElement, true)"); + res = utils.getParentForNode(text, true); + is(res.tagName, "BODY", "getParentForNode(text, true)"); + } + catch(e) { ok(false, "got an unexpected exception:" + e); } + + try { + var res = utils.getBindingURLs(docElement); + ok(SpecialPowers.call_Instanceof(res, SpecialPowers.Ci["nsIArray"]), "getBindingURLs result type"); + is(res.length, 0, "getBindingURLs array length"); + } + catch(e) { ok(false, "got an unexpected exception:" + e); } + + try { + utils.getContentState(docElement); + ok(true, "Should not throw"); + } + catch(e) { ok(false, "Got an exception: " + e); } + + try { + utils.setContentState(docElement, false); + ok(false, "expected an exception"); + } + catch(e) { + e = SpecialPowers.wrap(e); + is(e.result, ERROR_INVALID_ARG, "got the expected exception"); + } + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(do_test); + +</script> +</pre> +</body> +</html> diff --git a/layout/inspector/tests/test_bug522601.xhtml b/layout/inspector/tests/test_bug522601.xhtml new file mode 100644 index 000000000..7c5a9e79c --- /dev/null +++ b/layout/inspector/tests/test_bug522601.xhtml @@ -0,0 +1,274 @@ +<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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="testBinding"> + <content><div><children/></div><children includes="span"/></content> + </binding> + </bindings> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=522601">Mozilla Bug 522601</a> +<p id="display" style="-moz-binding: url(#testBinding)"> + <span 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></p> +<div id="content" style="display: none"> + +</div> +<div id="subdoc"> + <iframe id="frame1" src="file_bug522601.html">frame text</iframe> +</div> +<div id="test-shadow"> + <h2 id="h2">light child</h2> +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +/** Test for Bug 522601 **/ +SimpleTest.waitForExplicitFinish(); + +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"), NodeFilter.SHOW_ALL); + + 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"), NodeFilter.SHOW_ALL); + + 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"), NodeFilter.SHOW_ALL); + + 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"), NodeFilter.SHOW_ALL); + 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 = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes($("display")))[0]; + + try { + walkerNonAnon.currentNode = anonDiv; + ok(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 walkerAnon = + SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"] + .createInstance(SpecialPowers.Ci.inIDeepTreeWalker); + walkerAnon.showAnonymousContent = true; + walkerAnon.init($("display"), NodeFilter.SHOW_ALL); + + is(SpecialPowers.unwrap(walkerAnon.currentNode), $("display"), "Unexpected anon root"); + testFunc(walkerAnon, "nextNode", anonDiv, + "step to anonymous div"); + testFunc(walkerAnon, "nextNode", $("s").previousSibling, + "step to some 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", $("s"), "step to span (anon)"); + testFunc(walkerAnon, "nextNode", $("s").firstChild, "step to span text (anon)"); + testFunc(walkerAnon, "nextNode", null, "step past end (anon)"); + testFunc(walkerAnon, "parentNode", $("s"), "step up to span (anon)"); + testFunc(walkerAnon, "parentNode", $("display"), "step up to display (anon)"); + testFunc(walkerAnon, "nextNode", anonDiv, "step to anonymous div again"); + testFunc(walkerAnon, "parentNode", $("display"), + "step up to display again (anon)"); + testFunc(walkerAnon, "parentNode", null, "step up past root (anon)"); + testFunc(walkerAnon, "firstChild", anonDiv, + "step firstChild to display first child (anon)"); + testFunc(walkerAnon, "nextSibling", $("s"), + "step nextSibling to span (anon)"); + testFunc(walkerAnon, "nextSibling", null, "step nextSibling past end (anon)"); + testFunc(walkerAnon, "previousSibling", anonDiv, + "step previousSibling to anonymous div"); + testFunc(walkerAnon, "previousSibling", null, "step previousSibling past end (anon)"); + + // Move the walker over to the end + while (walkerAnon.nextNode()) { /* do nothing */ } + testFunc(walkerAnon, "previousNode", $("s"), "step back to span (anon)"); + testFunc(walkerAnon, "previousNode", $("i2").firstChild, + "step back to second italic text (anon)"); + 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").previousSibling, + "step back to some text (anon)"); + testFunc(walkerAnon, "previousNode", anonDiv, + "step back to anonymous div"); + testFunc(walkerAnon, "previousNode", $("display"), "step back to root (anon)"); + testFunc(walkerAnon, "previousNode", null, "step back past root (anon)"); + + var shadowdiv = document.querySelector('#test-shadow'); + var shadowRoot = shadowdiv.createShadowRoot(); + var h = document.createElement("header"); + var c = document.createElement("content"); + c.setAttribute("select", "h2"); + h.appendChild(c); + shadowRoot.appendChild(h); + + var walkerShadow = + SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"] + .createInstance(SpecialPowers.Ci.inIDeepTreeWalker); + walkerShadow.showAnonymousContent = true; + walkerShadow.init($("test-shadow"), NodeFilter.SHOW_ALL); + var c1 = walkerShadow.nextNode(); + var c2 = walkerShadow.nextNode(); + var c3 = walkerShadow.nextNode(); + + walkerShadow.currentNode = c1; + is(SpecialPowers.unwrap(walkerShadow.currentNode), h, + "Unexpected shadow element 1"); + walkerShadow.currentNode = c2; + is(SpecialPowers.unwrap(walkerShadow.currentNode), $("h2"), + "Unexpected shadow element 2"); + walkerShadow.currentNode = c3; + is(SpecialPowers.unwrap(walkerShadow.currentNode), $("h2").firstChild, + "Unexpected shadow element 3"); + + 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 000000000..6fe3d8f8d --- /dev/null +++ b/layout/inspector/tests/test_bug536379-2.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=536379 +--> +<head> + <title>Test for Bug 536379</title> + <script type="application/javascript" 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 "data:text/css,@import '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; + +var domUtils = + CC["@mozilla.org/inspector/dom-utils;1"].getService(CI.inIDOMUtils); +var rules = domUtils.getCSSStyleRules(document.getElementById("display")); +var firstPRule = + rules.GetElementAt(rules.Count() - 2).QueryInterface(CI.nsIDOMCSSStyleRule); +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 000000000..8ac012953 --- /dev/null +++ b/layout/inspector/tests/test_bug536379.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=536379 +--> +<head> + <title>Test for Bug 536379</title> + <script type="application/javascript" 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; + +var domUtils = + CC["@mozilla.org/inspector/dom-utils;1"].getService(CI.inIDOMUtils); +var rules = domUtils.getCSSStyleRules(document.getElementById("display")); +var firstPRule = + rules.GetElementAt(rules.Count() - 2).QueryInterface(CI.nsIDOMCSSStyleRule); +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.GetElementAt(rules.Count() - 1).QueryInterface(CI.nsIDOMCSSStyleRule); +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 000000000..eda46bdfd --- /dev/null +++ b/layout/inspector/tests/test_bug557726.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=557726 +--> +<head> + <title>Test for Bug 557726</title> + <script type="application/javascript" 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; + } + </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 **/ + +function getSelectors (rules) { + var styleElement = document.getElementById("pseudo-style"); + var selectors = []; + for (var i = 0; i < rules.Count(); i++) { + var rule = rules.GetElementAt(i).QueryInterface(SpecialPowers.Ci.nsIDOMCSSStyleRule); + if (SpecialPowers.unwrap(rule.parentStyleSheet.ownerNode) == styleElement) // no user agent rules + selectors.push(rule.selectorText); + } + return selectors; +} + +var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + +var div = document.getElementById("div1"); + +/* empty or missing pseudo-element argument */ +var selectors = getSelectors(domUtils.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(domUtils.getCSSStyleRules(div, null)); +is(selectors.length, 1, "pseudo-element argument can be null"); +is(selectors[0], "#div1", "should only have the non pseudo-element rule"); + +selectors = getSelectors(domUtils.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 = domUtils.getCSSStyleRules(div, "not a valid pseudo element"); +is(rules, null, "invalid pseudo-element returns no rules list"); + + +/* valid pseudo-element argument */ +selectors = getSelectors(domUtils.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(domUtils.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(domUtils.getCSSStyleRules(div, ":first-line")); +is(selectors.length, 0, "valid pseudo-element but no matching rules"); + +</script> +</pre> +</body> +</html> diff --git a/layout/inspector/tests/test_bug609549.xhtml b/layout/inspector/tests/test_bug609549.xhtml new file mode 100644 index 000000000..d7d115847 --- /dev/null +++ b/layout/inspector/tests/test_bug609549.xhtml @@ -0,0 +1,67 @@ +<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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="testBinding"> + <!-- No linebreaks since this is html and whitespace is preserved. --> + <content><div anonid="box-A">x</div><div anonid="box-B"><children includes="span"/></div><div anonid="box-C">x</div><children/></content> + </binding> + </bindings> +</head> + +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=609549">Mozilla Bug 609549</a> +<div id="bound" style="-moz-binding: url(#testBinding);"><p id="p">lorem ipsum dolor sit amet</p><span id="sandwiched">sandwiched</span></div> + +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +/** Test for Bug 609549 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + ok("getChildrenForNode" in domUtils, "domUtils has no getChildrenForNode"); + var withoutAnons = SpecialPowers.unwrap(domUtils.getChildrenForNode($("bound"), false)); + + is(withoutAnons.length, $("bound").childNodes.length, + "withoutAnons should be the same length as childNodes"); + is(withoutAnons[0], $("p"), "didn't get paragraph - without anons"); + is(withoutAnons[1], $("sandwiched"), + "didn't get sandwiched span - without anons"); + + var withAnons = domUtils.getChildrenForNode($("bound"), true); + + 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].id, "p", "didn't get paragraph - with anons"); + + var bKids = domUtils.getChildrenForNode(withAnons[1], true); + 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_bug806192.html b/layout/inspector/tests/test_bug806192.html new file mode 100644 index 000000000..2a19ab633 --- /dev/null +++ b/layout/inspector/tests/test_bug806192.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=806192 +--> +<head> + <title>Test for Bug 806192</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<pre id="test"> +<script type="application/javascript"> + +const utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] +.getService(SpecialPowers.Ci.inIDOMUtils); + +try { + var res = utils.getBindingURLs({}); + ok(false, "didn't get error"); +} +catch(e) { ok(true, "got expected exception"); } +</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 000000000..b07b6ec1d --- /dev/null +++ b/layout/inspector/tests/test_bug856317.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=856317 +--> +<head> + <title>Test for Bug 856317</title> + <script type="application/javascript" 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"> + +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 } +}; + +var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + +// 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(domUtils.getRuleLine(rule), expected.line + lineOffset, + "line number for "+rule.selectorText); + is(domUtils.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 000000000..0480656d4 --- /dev/null +++ b/layout/inspector/tests/test_bug877690.html @@ -0,0 +1,260 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=877690 +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 877690</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + +/** Test for Bug 877690 **/ + +// Returns true if array contains item. False otherwise. Raises an exception if +// array is not an Array object. If the item is found in array, remove item. +function contains(array, item) { + if (!array.indexOf) { + throw new "First argument is not an array"; + } + var index = array.indexOf(item); + if (index == -1) { + return false; + } + array.splice(index, 1); + return true; +} + +// Returns true if values contains all and only the expected values. False otherwise. +function testValues(values, expected) { + expected.forEach(function (expectedValue) { + if (!contains(values, expectedValue)) { + return false; + } + }); + return values.length === 0; +} + +function do_test() { + var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + var getCSSValuesForProperty = function(prop) { + return Array.from(utils.getCSSValuesForProperty(prop)); + } + + // test a property with keywords and colors + var prop = "color"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", + "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", + "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "currentColor", + "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", + "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", + "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", + "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", + "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", + "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred", + "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", + "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", + "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", + "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", + "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", + "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", + "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", + "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", + "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", + "steelblue", "tan", "teal", "thistle", "tomato", "transparent", "turquoise", "violet", "wheat", + "white", "whitesmoke", "yellow", "yellowgreen", "rgb", "hsl", "rgba", "hsla" ]; + ok(testValues(values, expected), "property color's values."); + + // test a shorthand property + var prop = "background"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", + "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", + "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "currentColor", "cyan", "darkblue", + "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", + "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", + "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", + "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", + "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred", + "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", + "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", + "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", + "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", + "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", + "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", + "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", + "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", + "powderblue", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", + "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", + "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "transparent", "turquoise", + "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen", "no-repeat", "repeat", + "repeat-x", "repeat-y", "space", "round", "fixed", "scroll", "local", "center", "top", "bottom", "left", "right", + "border-box", "padding-box", "content-box", "border-box", "padding-box", "content-box", "text", "contain", + "cover", "rgb", "hsl", "rgba", "hsla", "none", "-moz-element", "-moz-image-rect", "url", "linear-gradient", + "radial-gradient", "repeating-linear-gradient", "repeating-radial-gradient", "-moz-linear-gradient", + "-moz-radial-gradient", "-moz-repeating-linear-gradient", "-moz-repeating-radial-gradient" ]; + ok(testValues(values, expected), "Shorthand property values."); + + var prop = "border"; + var values = getCSSValuesForProperty(prop); + var expected = [ "-moz-calc", "initial", "unset", "aliceblue", + "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", + "brown", "burlywood", "cadetblue", "calc", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", + "crimson", "currentColor", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", + "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", + "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "dashed", "deeppink", + "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "dotted", "double", "fill", "firebrick", "floralwhite", + "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "grey", "green", "greenyellow", + "groove", "hidden", "honeydew", "hotpink", "hsl", "hsla", "indianred", "indigo", "inherit", "inset", "ivory", + "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", + "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", + "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", + "linen", "logical", "magenta", "maroon", "medium", "mediumaquamarine", "mediumblue", "mediumorchid", + "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", + "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "none", + "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "outset", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "physical", "pink", "plum", "powderblue", + "purple", "rebeccapurple", "red", "repeat", "rgb", "rgba", "ridge", "rosybrown", "round", "royalblue", "saddlebrown", + "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", + "snow", "solid", "space", "springgreen", "steelblue", "stretch", "tan", "teal", "thick", "thin", "thistle", "tomato", + "transparent", "turquoise", "-moz-element", "-moz-image-rect", "url", "violet", "wheat", "white", "whitesmoke", + "yellow", "yellowgreen", "linear-gradient", "radial-gradient", "repeating-linear-gradient", + "repeating-radial-gradient", "-moz-linear-gradient", "-moz-radial-gradient", "-moz-repeating-linear-gradient", + "-moz-repeating-radial-gradient" ] + ok(testValues(values, expected), "Shorthand property values."); + + // test keywords only + var prop = "border-top"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "thin", "medium", "thick", "none", "hidden", "dotted", + "dashed", "solid", "double", "groove", "ridge", "inset", "outset", + "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", + "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", + "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "currentColor", "cyan", "darkblue", "darkcyan", + "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", + "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", + "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", + "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", + "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred", + "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", + "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", + "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", + "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", + "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", + "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", + "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", + "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", + "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", + "steelblue", "tan", "teal", "thistle", "tomato", "transparent", "turquoise", "violet", "wheat", "white", + "whitesmoke", "yellow", "yellowgreen", "calc", "-moz-calc", "rgb", "hsl", "rgba", "hsla" ]; + ok(testValues(values, expected), "property border-top's values."); + + // tests no keywords or colors + var prop = "padding-bottom"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "calc", "-moz-calc" ]; + ok(testValues(values, expected), "property padding-bottom's values."); + + // test proprety + var prop = "display"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "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", "-moz-box", "-moz-inline-box", + "-moz-grid", "-moz-inline-grid", "-moz-grid-group", "-moz-grid-line", "-moz-stack", "-moz-inline-stack", + "-moz-deck", "-moz-popup", "-moz-groupbox", + "flex", "inline-flex", "-webkit-box", "-webkit-inline-box", + "-webkit-flex", "-webkit-inline-flex", + "grid", "inline-grid", + "ruby", "ruby-base", "ruby-base-container", "ruby-text", "ruby-text-container", + "contents" ]; + ok(testValues(values, expected), "property display's values."); + + // test property + var prop = "float"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "none", "left", "right", "inline-start", "inline-end" ]; + ok(testValues(values, expected), "property float's values."); + + // Test property with "auto" + var prop = "margin"; + var values = getCSSValuesForProperty(prop); + var expected = [ "-moz-calc", "initial", "unset", "auto", "calc", "inherit" ]; + ok(testValues(values, expected), "property margin's values."); + + // Test property with "normal" + var prop = "font-style"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "inherit", "unset", "italic", "normal", "oblique" ]; + ok(testValues(values, expected), "property font-style's values."); + + // Test property with "cubic-bezier" and "step". + var prop = "-moz-transition"; + var values = getCSSValuesForProperty(prop); + var expected = [ "initial", "all", "unset", "cubic-bezier", "ease", "ease-in", "ease-in-out", + "ease-out", "inherit", "linear", "none", "step-end", "step-start", + "steps" ]; + ok(testValues(values, expected), "property -moz-transition's values."); + + // test invalid property + var prop = "invalidProperty"; + try { + getCSSValuesForProperty(prop); + ok(false, "invalid property should throw an exception"); + } + catch(e) { + // test passed + } + + // test border-image propery, for bug 973345 + var prop = "border-image"; + var values = getCSSValuesForProperty(prop); + var expected = [ "-moz-calc", "initial", "unset", "aliceblue", + "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", + "brown", "burlywood", "cadetblue", "calc", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", + "crimson", "currentColor", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", + "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", + "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "dashed", "deeppink", + "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "dotted", "double", "fill", "firebrick", "floralwhite", + "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "grey", "green", "greenyellow", + "groove", "hidden", "honeydew", "hotpink", "hsl", "hsla", "indianred", "indigo", "inherit", "inset", "ivory", + "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", + "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", + "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", + "linen", "logical", "magenta", "maroon", "medium", "mediumaquamarine", "mediumblue", "mediumorchid", + "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", + "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "none", + "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "outset", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "physical", "pink", "plum", "powderblue", + "purple", "rebeccapurple", "red", "repeat", "rgb", "rgba", "ridge", "rosybrown", "round", "royalblue", "saddlebrown", + "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", + "snow", "solid", "springgreen", "steelblue", "stretch", "tan", "teal", "thick", "thin", "thistle", "tomato", + "transparent", "turquoise", "-moz-element", "-moz-image-rect", "url", "violet", "wheat", "white", "whitesmoke", + "yellow", "yellowgreen", "linear-gradient", "radial-gradient", "repeating-linear-gradient", + "repeating-radial-gradient", "-moz-linear-gradient", "-moz-radial-gradient", "-moz-repeating-linear-gradient", + "-moz-repeating-radial-gradient" ] + ok(testValues(values, expected), "property border-image's values."); + + 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 000000000..d8279e384 --- /dev/null +++ b/layout/inspector/tests/test_color_to_rgba.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test inDOMUtils::ColorToRGBA</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript;version=1.8"> + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + 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:17, b:28, a:1}); + testColor("hsla(320,30%,10%)", {r:33, g:17, b:28, a:1}); + testColor("hsl(170,60%,40%,0.9)", {r:40, g:163, b:142, a:0.9}); + testColor("hsla(170,60%,40%,0.9)", {r:40, g:163, b:142, a:0.9}); + + function testColor(color, expected) { + let rgb = utils.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 inDOMUtils::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_css_property_is_shorthand.html b/layout/inspector/tests/test_css_property_is_shorthand.html new file mode 100644 index 000000000..e5fbcc1c9 --- /dev/null +++ b/layout/inspector/tests/test_css_property_is_shorthand.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test inDOMUtils::CssPropertyIsShorthand</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript;version=1.8"> + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + let tests = [ + { + property: "color", + expected: false + }, + { + property: "background", + expected: true + }, + { + property: "--anything", + expected: false + } + ]; + + for (let {property, expected} of tests) { + let result = utils.cssPropertyIsShorthand(property); + is(result, expected, "checking whether " + property + " is shorthand"); + } + + let sawException = false; + try { + let result = utils.cssPropertyIsShorthand("nosuchproperty"); + } catch (e) { + sawException = true; + } + ok(sawException, "checking whether nosuchproperty throws"); + + </script> +</head> +<body> +<h1>Test inDOMUtils::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_css_property_is_valid.html b/layout/inspector/tests/test_css_property_is_valid.html new file mode 100644 index 000000000..21f00bf18 --- /dev/null +++ b/layout/inspector/tests/test_css_property_is_valid.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test inDOMUtils::CssPropertyIsValid</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript;version=1.8"> + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + let tests = [ + { + property: "color", + value: "red", + expected: true + }, + { + property: "display", + value: "none", + expected: true + }, + { + property: "display", + value: "red", + expected: false + }, + { + property: "displayx", + value: "none", + expected: false + }, + { + property: "border", + value: "1px solid blue", + expected: true + }, + { + property: "border", + value: "1 solid blue", + expected: false + }, + { + property: "border", + value: "1px underline blue", + expected: false + }, + { + property: "border", + value: "1px solid", + expected: true + }, + { + property: "color", + value: "blue !important", + expected: true + }, + { + property: "color", + value: "blue ! important", + expected: true + }, + { + property: "color", + value: "blue !impoxxxrtant", + expected: false + }, + { + property: "color", + value: "red; background:green;", + expected: false + }, + { + property: "content", + value: "\"hello\"", + expected: true + }, + { + property: "color", + value: "var(--some-kind-of-green)", + expected: true + } + ]; + + for (let {property, value, expected} of tests) { + let valid = utils.cssPropertyIsValid(property, value); + + if (expected) { + ok(valid, property + ":" + value + " is valid"); + } else { + ok(!valid, property + ":" + value + " is not valid"); + } + } + </script> +</head> +<body> +<h1>Test inDOMUtils::CssPropertyIsValid</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 000000000..cdb4572d4 --- /dev/null +++ b/layout/inspector/tests/test_getCSSPseudoElementNames.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test inDOMUtils::getCSSPseudoElementNames</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript;version=1.8"> + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + let expected = new Set([ + ":after", + ":before", + ":backdrop", + ":first-letter", + ":first-line", + ":placeholder", + ":-moz-color-swatch", + ":-moz-focus-inner", + ":-moz-focus-outer", + ":-moz-list-bullet", + ":-moz-list-number", + ":-moz-math-anonymous", + ":-moz-meter-bar", + ":-moz-placeholder", + ":-moz-progress-bar", + ":-moz-range-progress", + ":-moz-range-thumb", + ":-moz-range-track", + ":-moz-selection", + ]); + + let names = utils.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 inDOMUtils::getCSSPseudoElementNames</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_getRelativeRuleLine.html b/layout/inspector/tests/test_getRelativeRuleLine.html new file mode 100644 index 000000000..a1481a49d --- /dev/null +++ b/layout/inspector/tests/test_getRelativeRuleLine.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test inDOMUtils::getRelativeRuleLine</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" 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> + <script type="application/javascript;version=1.8"> + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + let tests = [ + { sheetNo: 0, ruleNo: 0, lineNo: 1, columnNo: 1 }, + { sheetNo: 1, ruleNo: 0, lineNo: 2, columnNo: 15 }, + { sheetNo: 1, ruleNo: 1, lineNo: 8, columnNo: 5 }, + { sheetNo: 2, ruleNo: 0, lineNo: 1, columnNo: 1 }, + { sheetNo: 2, ruleNo: 1, lineNo: 0, columnNo: 1 }, + { sheetNo: 3, ruleNo: 0, lineNo: 5, columnNo: 6 }, + ]; + + function doTest() { + document.styleSheets[2].insertRule("body{}", 1); + for (let test of tests) { + let sheet = document.styleSheets[test.sheetNo]; + let rule = sheet.cssRules[test.ruleNo]; + let line = utils.getRelativeRuleLine(rule); + let column = utils.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 inDOMUtils::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_get_all_style_sheets.html b/layout/inspector/tests/test_get_all_style_sheets.html new file mode 100644 index 000000000..7e5a4d49b --- /dev/null +++ b/layout/inspector/tests/test_get_all_style_sheets.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=734861 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 734861</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 734861 **/ + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + var res = utils.getAllStyleSheets(document); + + var foundUA = false; + for (var i = 0; i < res.length; i++) { + if (res[i].href === "resource://gre-resources/ua.css") { + foundUA = true; + break; + } + } + ok(foundUA, "UA sheet should be returned with all the other sheets."); + SimpleTest.finish(); + } + + </script> +</head> +<body onload="runTest();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=734861">Mozilla Bug 734861</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_is_valid_css_color.html b/layout/inspector/tests/test_is_valid_css_color.html new file mode 100644 index 000000000..ef5eef32e --- /dev/null +++ b/layout/inspector/tests/test_is_valid_css_color.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test inDOMUtils::isValidCSSColor</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript;version=1.8"> + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + // Color names + let colors = utils.getCSSValuesForProperty("color"); + let notColor = ["hsl", "hsla", "inherit", "initial", "rgb", "rgba", + "unset", "transparent", "currentColor"]; + for (let color of colors) { + if (notColor.indexOf(color) !== -1) { + continue; + } + ok(utils.isValidCSSColor(color), color + " is a valid color"); + ok(!utils.isValidCSSColor("xxx" + color), "xxx" + color + " is not a valid color"); + } + + // rgb(a) + for (let i = 0; i <= 265; i++) { + ok(utils.isValidCSSColor("rgb(" + i + ",0,0)"), "rgb(" + i + ",0,0) is a valid color"); + ok(utils.isValidCSSColor("rgb(0," + i + ",0)"), "rgb(0," + i + ",0) is a valid color"); + ok(utils.isValidCSSColor("rgb(0,0," + i + ")"), "rgb(0,0," + i + ") is a valid color"); + ok(utils.isValidCSSColor("rgba(" + i + ",0,0,0.2)"), "rgba(" + i + ",0,0,0.2) is a valid color"); + ok(utils.isValidCSSColor("rgba(0," + i + ",0,0.5)"), "rgba(0," + i + ",0,0.5) is a valid color"); + ok(utils.isValidCSSColor("rgba(0,0," + i + ",0.7)"), "rgba(0,0," + i + ",0.7) is a valid color"); + + ok(!utils.isValidCSSColor("rgbxxx(" + i + ",0,0)"), "rgbxxx(" + i + ",0,0) is not a valid color"); + ok(!utils.isValidCSSColor("rgbxxx(0," + i + ",0)"), "rgbxxx(0," + i + ",0) is not a valid color"); + ok(!utils.isValidCSSColor("rgbxxx(0,0," + i + ")"), "rgbxxx(0,0," + i + ") is not a valid color"); + } + + // rgb(a) (%) + for (let i = 0; i <= 110; i++) { + ok(utils.isValidCSSColor("rgb(" + i + "%,0%,0%)"), "rgb(" + i + "%,0%,0%) is a valid color"); + ok(utils.isValidCSSColor("rgb(0%," + i + "%,0%)"), "rgb(0%," + i + "%,0%) is a valid color"); + ok(utils.isValidCSSColor("rgb(0%,0%," + i + "%)"), "rgb(0%,0%," + i + "%) is a valid color"); + ok(utils.isValidCSSColor("rgba(" + i + "%,0%,0%,0.2)"), "rgba(" + i + "%,0%,0%,0.2) is a valid color"); + ok(utils.isValidCSSColor("rgba(0%," + i + "%,0%,0.5)"), "rgba(0%," + i + "%,0%,0.5) is a valid color"); + ok(utils.isValidCSSColor("rgba(0%,0%," + i + "%,0.7)"), "rgba(0%,0%," + i + "%,0.7) is a valid color"); + + ok(!utils.isValidCSSColor("rgbaxxx(" + i + "%,0%,0%,0.2)"), "rgbaxxx(" + i + "%,0%,0%,0.2) is not a valid color"); + ok(!utils.isValidCSSColor("rgbaxxx(0%," + i + "%,0%,0.5)"), "rgbaxxx(0%," + i + "%,0%,0.5) is not a valid color"); + ok(!utils.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(utils.isValidCSSColor("hsl(" + i + ",30%,10%)"), "rgb(" + i + ",30%,10%) is a valid color"); + ok(utils.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(utils.isValidCSSColor("hsl(100," + i + "%,20%)"), "hsl(100," + i + "%,20%) is a valid color"); + ok(utils.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(utils.isValidCSSColor("#" + hex + "7777"), "#" + hex + "7777 is a valid color"); + ok(utils.isValidCSSColor("#77" + hex + "77"), "#77" + hex + "77 is a valid color"); + ok(utils.isValidCSSColor("#7777" + hex), "#7777" + hex + " is a valid color"); + } + ok(!utils.isValidCSSColor("#kkkkkk"), "#kkkkkk is not a valid color"); + + // short hex + for (let i = 0; i <= 16; i++) { + let hex = (i).toString(16); + ok(utils.isValidCSSColor("#" + hex + hex + hex), "#" + hex + hex + hex + " is a valid color"); + } + ok(!utils.isValidCSSColor("#ggg"), "#ggg is not a valid color"); + </script> +</head> +<body> +<h1>Test inDOMUtils::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 000000000..6bbb1650f --- /dev/null +++ b/layout/inspector/tests/test_isinheritableproperty.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=699592 +--> +<head> + <title>Test for nsIDOMUtils::isInheritedProperty</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<pre id="test"> +<script type="application/javascript"> + +function do_test() { + var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + is(utils.isInheritedProperty("font-size"), true, "font-size is inherited."); + + is(utils.isInheritedProperty("min-width"), false, "min-width is not inherited."); + + is(utils.isInheritedProperty("font"), true, "shorthand font property is inherited."); + + is(utils.isInheritedProperty("border"), false, "shorthand border property not inherited."); + is(utils.isInheritedProperty("garbage"), false, "Unknown property isn't inherited."); + + 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 000000000..f7ddf6e9e --- /dev/null +++ b/layout/inspector/tests/test_parseStyleSheet.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=1195978 +--> + <head> + <title>Test for Bug 1195978</title> + <script type="application/javascript" 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"> +var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + +var sheet = document.styleSheets[1]; +is(domUtils.getRelativeRuleLine(sheet.cssRules[0]), 2, + "initial relative rule line"); +is(domUtils.getRuleLine(sheet.cssRules[0]), 11, + "initial rule line"); + +domUtils.parseStyleSheet(sheet, "\nbody {\n color: blue;\n}\n"); +is(domUtils.getRelativeRuleLine(sheet.cssRules[0]), 2, + "relative rule line after reparsing"); +is(domUtils.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 000000000..73fef2d51 --- /dev/null +++ b/layout/inspector/tests/test_parseStyleSheetImport.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=1202095 +--> + <head> + <title>Test for Bug 1202095</title> + <script type="application/javascript" 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"> +var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + +function do_test() { + var sheet = document.styleSheets[1]; + var importRule = sheet.cssRules[0]; + is(importRule.type, SpecialPowers.Ci.nsIDOMCSSRule.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; + domUtils.parseStyleSheet(suffixedSheet, ""); + is(suffixedSheet.cssRules.length, 0, "second imported sheet now has no rules"); + + // Re-parse the style sheet, preserving the imports. + domUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');" + + "@import url('bug1202095-2.css');"); + is(sheet.cssRules[0].type, SpecialPowers.Ci.nsIDOMCSSRule.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. + domUtils.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. + domUtils.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. + domUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');"); + is(sheet.cssRules[0].type, SpecialPowers.Ci.nsIDOMCSSRule.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; + domUtils.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_selectormatcheselement.html b/layout/inspector/tests/test_selectormatcheselement.html new file mode 100644 index 000000000..a052f74d1 --- /dev/null +++ b/layout/inspector/tests/test_selectormatcheselement.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1037519 +--> +<head> + <title>Test for nsIDOMUtils::selectorMatchesElement</title> + <script type="application/javascript" 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::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"> + +function do_test() { + var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + var element = document.querySelector("#foo"); + + var elementRules = utils.getCSSStyleRules(element); + var elementRule = elementRules.GetElementAt(elementRules.Count() - 1); + + is (utils.selectorMatchesElement(element, elementRule, 0), true, + "Matches #foo"); + is (utils.selectorMatchesElement(element, elementRule, 1), false, + "Doesn't match #bar"); + is (utils.selectorMatchesElement(element, elementRule, 0, ":bogus"), false, + "Doesn't match #foo with a bogus pseudo"); + is (utils.selectorMatchesElement(element, elementRule, 2, ":bogus"), false, + "Doesn't match #foo::before with bogus pseudo"); + is (utils.selectorMatchesElement(element, elementRule, 0, ":after"), false, + "Does match #foo::before with the :after pseudo"); + + checkPseudo(":before"); + checkPseudo(":after"); + checkPseudo(":first-letter"); + checkPseudo(":first-line"); + + SimpleTest.finish(); + + function checkPseudo(pseudo) { + var rules = utils.getCSSStyleRules(element, pseudo); + var rule = rules.GetElementAt(rules.Count() - 1); + + is (utils.selectorMatchesElement(element, rule, 0), false, + "Doesn't match without " + pseudo); + is (utils.selectorMatchesElement(element, rule, 1), false, + "Doesn't match without " + pseudo); + + is (utils.selectorMatchesElement(element, rule, 0, pseudo), true, + "Matches on #foo" + pseudo); + is (utils.selectorMatchesElement(element, rule, 1, pseudo), false, + "Doesn't match on #bar" + pseudo); + } +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(do_test); + +</script> +</pre> +</body> +</html> |