/* -*- 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 "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject #include "js/JSON.h" #include "js/PropertyAndElement.h" // JS_GetElement #include "js/TypeDecls.h" #include "jsapi.h" #include "mozilla/PresShell.h" #include "mozilla/dom/AutocompleteInfoBinding.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLSelectElement.h" #include "mozilla/dom/HTMLTextAreaElement.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/SessionStorageManager.h" #include "mozilla/dom/PBackgroundSessionStorageCache.h" #include "mozilla/dom/SessionStoreChangeListener.h" #include "mozilla/dom/SessionStoreChild.h" #include "mozilla/dom/SessionStoreUtils.h" #include "mozilla/dom/txIXPathContext.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WindowProxyHolder.h" #include "mozilla/dom/XPathResult.h" #include "mozilla/dom/XPathEvaluator.h" #include "mozilla/dom/XPathExpression.h" #include "mozilla/dom/PBackgroundSessionStorageCache.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ReverseIterator.h" #include "mozilla/UniquePtr.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentList.h" #include "nsContentUtils.h" #include "nsFocusManager.h" #include "nsGlobalWindowOuter.h" #include "nsIContentInlines.h" #include "nsIDocShell.h" #include "nsIFormControl.h" #include "nsIScrollableFrame.h" #include "nsISHistory.h" #include "nsIXULRuntime.h" #include "nsPresContext.h" #include "nsPrintfCString.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::sessionstore; namespace { class DynamicFrameEventFilter final : public nsIDOMEventListener { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter) explicit DynamicFrameEventFilter(EventListener* aListener) : mListener(aListener) {} NS_IMETHODIMP HandleEvent(Event* aEvent) override { if (mListener && TargetInNonDynamicDocShell(aEvent)) { mListener->HandleEvent(*aEvent); } return NS_OK; } private: ~DynamicFrameEventFilter() = default; bool TargetInNonDynamicDocShell(Event* aEvent) { EventTarget* target = aEvent->GetTarget(); if (!target) { return false; } nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal(); if (!outer || !outer->GetDocShell()) { return false; } RefPtr context = outer->GetBrowsingContext(); return context && !context->CreatedDynamically(); } RefPtr mListener; }; NS_IMPL_CYCLE_COLLECTION(DynamicFrameEventFilter, mListener) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicFrameEventFilter) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(DynamicFrameEventFilter) NS_IMPL_CYCLE_COLLECTING_RELEASE(DynamicFrameEventFilter) } // anonymous namespace /* static */ void SessionStoreUtils::ForEachNonDynamicChildFrame( const GlobalObject& aGlobal, WindowProxyHolder& aWindow, SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv) { if (!aWindow.get()) { aRv.Throw(NS_ERROR_INVALID_ARG); return; } nsCOMPtr docShell = aWindow.get()->GetDocShell(); if (!docShell) { aRv.Throw(NS_ERROR_FAILURE); return; } int32_t length; aRv = docShell->GetInProcessChildCount(&length); if (aRv.Failed()) { return; } for (int32_t i = 0; i < length; ++i) { nsCOMPtr item; docShell->GetInProcessChildAt(i, getter_AddRefs(item)); if (!item) { aRv.Throw(NS_ERROR_FAILURE); return; } RefPtr context = item->GetBrowsingContext(); if (!context) { aRv.Throw(NS_ERROR_FAILURE); return; } if (!context->CreatedDynamically()) { int32_t childOffset = context->ChildOffset(); aCallback.Call(WindowProxyHolder(context.forget()), childOffset); } } } /* static */ already_AddRefed SessionStoreUtils::AddDynamicFrameFilteredListener( const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType, JS::Handle aListener, bool aUseCapture, bool aMozSystemGroup, ErrorResult& aRv) { if (NS_WARN_IF(!aListener.isObject())) { aRv.Throw(NS_ERROR_INVALID_ARG); return nullptr; } JSContext* cx = aGlobal.Context(); JS::Rooted obj(cx, &aListener.toObject()); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); RefPtr listener = new EventListener(cx, obj, global, GetIncumbentGlobal()); nsCOMPtr filter(new DynamicFrameEventFilter(listener)); if (aMozSystemGroup) { aRv = aTarget.AddSystemEventListener(aType, filter, aUseCapture); } else { aRv = aTarget.AddEventListener(aType, filter, aUseCapture); } if (aRv.Failed()) { return nullptr; } return filter.forget(); } /* static */ void SessionStoreUtils::RemoveDynamicFrameFilteredListener( const GlobalObject& global, EventTarget& aTarget, const nsAString& aType, nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup, ErrorResult& aRv) { nsCOMPtr listener = do_QueryInterface(aListener); if (!listener) { aRv.Throw(NS_ERROR_NO_INTERFACE); return; } if (aMozSystemGroup) { aTarget.RemoveSystemEventListener(aType, listener, aUseCapture); } else { aTarget.RemoveEventListener(aType, listener, aUseCapture); } } /* static */ void SessionStoreUtils::CollectDocShellCapabilities(const GlobalObject& aGlobal, nsIDocShell* aDocShell, nsCString& aRetVal) { bool allow; #define TRY_ALLOWPROP(y) \ PR_BEGIN_MACRO \ aDocShell->GetAllow##y(&allow); \ if (!allow) { \ if (!aRetVal.IsEmpty()) { \ aRetVal.Append(','); \ } \ aRetVal.Append(#y); \ } \ PR_END_MACRO TRY_ALLOWPROP(Plugins); // Bug 1328013 : Don't collect "AllowJavascript" property // TRY_ALLOWPROP(Javascript); TRY_ALLOWPROP(MetaRedirects); TRY_ALLOWPROP(Subframes); TRY_ALLOWPROP(Images); TRY_ALLOWPROP(Media); TRY_ALLOWPROP(DNSPrefetch); TRY_ALLOWPROP(WindowControl); TRY_ALLOWPROP(Auth); TRY_ALLOWPROP(ContentRetargeting); TRY_ALLOWPROP(ContentRetargetingOnChildren); #undef TRY_ALLOWPROP } /* static */ void SessionStoreUtils::RestoreDocShellCapabilities( nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities) { aDocShell->SetAllowPlugins(true); aDocShell->SetAllowMetaRedirects(true); aDocShell->SetAllowSubframes(true); aDocShell->SetAllowImages(true); aDocShell->SetAllowMedia(true); aDocShell->SetAllowDNSPrefetch(true); aDocShell->SetAllowWindowControl(true); aDocShell->SetAllowContentRetargeting(true); aDocShell->SetAllowContentRetargetingOnChildren(true); bool allowJavascript = true; for (const nsACString& token : nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) { if (token.EqualsLiteral("Plugins")) { aDocShell->SetAllowPlugins(false); } else if (token.EqualsLiteral("Javascript")) { allowJavascript = false; } else if (token.EqualsLiteral("MetaRedirects")) { aDocShell->SetAllowMetaRedirects(false); } else if (token.EqualsLiteral("Subframes")) { aDocShell->SetAllowSubframes(false); } else if (token.EqualsLiteral("Images")) { aDocShell->SetAllowImages(false); } else if (token.EqualsLiteral("Media")) { aDocShell->SetAllowMedia(false); } else if (token.EqualsLiteral("DNSPrefetch")) { aDocShell->SetAllowDNSPrefetch(false); } else if (token.EqualsLiteral("WindowControl")) { aDocShell->SetAllowWindowControl(false); } else if (token.EqualsLiteral("ContentRetargeting")) { bool allow; aDocShell->GetAllowContentRetargetingOnChildren(&allow); aDocShell->SetAllowContentRetargeting( false); // will also set AllowContentRetargetingOnChildren aDocShell->SetAllowContentRetargetingOnChildren( allow); // restore the allowProp to original } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) { aDocShell->SetAllowContentRetargetingOnChildren(false); } } if (!mozilla::SessionHistoryInParent()) { // With SessionHistoryInParent, this is set from the parent process. BrowsingContext* bc = aDocShell->GetBrowsingContext(); Unused << bc->SetAllowJavascript(allowJavascript); } } static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument, Nullable& aRetVal) { PresShell* presShell = aDocument.GetPresShell(); if (!presShell) { return; } nsPoint scrollPos = presShell->GetVisualViewportOffset(); int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x); int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y); if ((scrollX != 0) || (scrollY != 0)) { aRetVal.SetValue().mScroll.Construct() = nsPrintfCString("%d,%d", scrollX, scrollY); } } /* static */ void SessionStoreUtils::RestoreScrollPosition(const GlobalObject& aGlobal, nsGlobalWindowInner& aWindow, const CollectedData& aData) { if (aData.mScroll.WasPassed()) { RestoreScrollPosition(aWindow, aData.mScroll.Value()); } } /* static */ void SessionStoreUtils::RestoreScrollPosition( nsGlobalWindowInner& aWindow, const nsCString& aScrollPosition) { using Change = mozilla::dom::SessionStoreChangeListener::Change; SessionStoreChangeListener::CollectSessionStoreData( aWindow.GetWindowContext(), EnumSet(Change::Scroll)); nsCCharSeparatedTokenizer tokenizer(aScrollPosition, ','); nsAutoCString token(tokenizer.nextToken()); int pos_X = atoi(token.get()); token = tokenizer.nextToken(); int pos_Y = atoi(token.get()); aWindow.ScrollTo(pos_X, pos_Y); if (nsCOMPtr doc = aWindow.GetExtantDoc()) { if (nsPresContext* presContext = doc->GetPresContext()) { if (presContext->IsRootContentDocumentCrossProcess()) { // Use eMainThread so this takes precedence over session history // (ScrollFrameHelper::ScrollToRestoredPosition()). presContext->PresShell()->ScrollToVisual( CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)), layers::FrameMetrics::eMainThread, ScrollMode::Instant); } } } } // Implements the Luhn checksum algorithm as described at // http://wikipedia.org/wiki/Luhn_algorithm // Number digit lengths vary with network, but should fall within 12-19 range. // [2] More details at https://en.wikipedia.org/wiki/Payment_card_number static bool IsValidCCNumber(nsAString& aValue) { uint32_t total = 0; uint32_t numLength = 0; uint32_t strLen = aValue.Length(); for (uint32_t i = 0; i < strLen; ++i) { uint32_t idx = strLen - i - 1; // ignore whitespace and dashes) char16_t chr = aValue[idx]; if (IsSpaceCharacter(chr) || chr == '-') { continue; } // If our number is too long, note that fact ++numLength; if (numLength > 19) { return false; } // Try to parse the character as a base-10 integer. nsresult rv = NS_OK; uint32_t val = Substring(aValue, idx, 1).ToInteger(&rv, 10); if (NS_FAILED(rv)) { return false; } if (i % 2 == 1) { val *= 2; if (val > 9) { val -= 9; } } total += val; } return numLength >= 12 && total % 10 == 0; } // Limit the number of XPath expressions for performance reasons. See bug // 477564. static const uint16_t kMaxTraversedXPaths = 100; // A helper function to append a element into mId or mXpath of CollectedData static Record::EntryType* AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId, uint16_t& aGeneratedCount, Nullable& aRetVal) { Record::EntryType* entry; if (!aId.IsEmpty()) { if (!aRetVal.SetValue().mId.WasPassed()) { aRetVal.SetValue().mId.Construct(); } auto& recordEntries = aRetVal.SetValue().mId.Value().Entries(); entry = recordEntries.AppendElement(); entry->mKey = aId; } else { if (!aRetVal.SetValue().mXpath.WasPassed()) { aRetVal.SetValue().mXpath.Construct(); } auto& recordEntries = aRetVal.SetValue().mXpath.Value().Entries(); entry = recordEntries.AppendElement(); nsAutoString xpath; aNode->GenerateXPath(xpath); aGeneratedCount++; entry->mKey = xpath; } return entry; } /* for bool value */ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId, const bool& aValue, uint16_t& aGeneratedCount, JSContext* aCx, Nullable& aRetVal) { Record::EntryType* entry = AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); entry->mValue.SetAsBoolean() = aValue; } /* for nsString value */ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId, const nsString& aValue, uint16_t& aGeneratedCount, Nullable& aRetVal) { Record::EntryType* entry = AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); entry->mValue.SetAsString() = aValue; } /* for single select value */ static void AppendValueToCollectedData( nsINode* aNode, const nsAString& aId, const CollectedNonMultipleSelectValue& aValue, uint16_t& aGeneratedCount, JSContext* aCx, Nullable& aRetVal) { JS::Rooted jsval(aCx); if (!ToJSValue(aCx, aValue, &jsval)) { JS_ClearPendingException(aCx); return; } Record::EntryType* entry = AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); entry->mValue.SetAsObject() = &jsval.toObject(); } /* special handing for input element with string type */ static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode, const nsAString& aId, const nsString& aValue, uint16_t& aGeneratedCount, JSContext* aCx, Nullable& aRetVal) { if (!aId.IsEmpty()) { // We want to avoid saving data for about:sessionrestore as a string. // Since it's stored in the form as stringified JSON, stringifying // further causes an explosion of escape characters. cf. bug 467409 if (aId.EqualsLiteral("sessionData")) { nsAutoCString url; Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url); if (url.EqualsLiteral("about:sessionrestore") || url.EqualsLiteral("about:welcomeback")) { JS::Rooted jsval(aCx); if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) && jsval.isObject()) { Record::EntryType* entry = AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); entry->mValue.SetAsObject() = &jsval.toObject(); } else { JS_ClearPendingException(aCx); } return; } } } AppendValueToCollectedData(aNode, aId, aValue, aGeneratedCount, aRetVal); } /* for nsTArray: file and multipleSelect */ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId, const nsAString& aValueType, nsTArray& aValue, uint16_t& aGeneratedCount, JSContext* aCx, Nullable& aRetVal) { JS::Rooted jsval(aCx); if (aValueType.EqualsLiteral("file")) { CollectedFileListValue val; val.mType = aValueType; val.mFileList = std::move(aValue); if (!ToJSValue(aCx, val, &jsval)) { JS_ClearPendingException(aCx); return; } } else { if (!ToJSValue(aCx, aValue, &jsval)) { JS_ClearPendingException(aCx); return; } } Record::EntryType* entry = AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); entry->mValue.SetAsObject() = &jsval.toObject(); } // This isn't size as in binary size, just a heuristic to not store too large // fields in session store. See StaticPrefs::browser_sessionstore_dom_form_limit static uint32_t SizeOfFormEntry(const FormEntryValue& aValue) { uint32_t size = 0; switch (aValue.type()) { case FormEntryValue::TCheckbox: size = aValue.get_Checkbox().value() ? 4 : 5; break; case FormEntryValue::TTextField: size = aValue.get_TextField().value().Length(); break; case FormEntryValue::TFileList: { for (const auto& value : aValue.get_FileList().valueList()) { size += value.Length(); } break; } case FormEntryValue::TSingleSelect: size = aValue.get_SingleSelect().value().Length(); break; case FormEntryValue::TMultipleSelect: { for (const auto& value : aValue.get_MultipleSelect().valueList()) { size += value.Length(); } break; } default: break; } return size; } static uint32_t AppendEntry(nsINode* aNode, const nsString& aId, const FormEntryValue& aValue, sessionstore::FormData& aFormData) { uint32_t size = SizeOfFormEntry(aValue); if (size > StaticPrefs::browser_sessionstore_dom_form_limit()) { return 0; } if (aId.IsEmpty()) { FormEntry* entry = aFormData.xpath().AppendElement(); entry->value() = aValue; aNode->GenerateXPath(entry->id()); size += entry->id().Length(); } else { aFormData.id().AppendElement(FormEntry{aId, aValue}); size += aId.Length(); } return size; } static uint32_t CollectTextAreaElement(Document* aDocument, sessionstore::FormData& aFormData) { uint32_t size = 0; RefPtr textlist = NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"textarea"_ns); uint32_t length = textlist->Length(); for (uint32_t i = 0; i < length; ++i) { MOZ_ASSERT(textlist->Item(i), "null item in node list!"); HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i)); if (!textArea) { continue; } DOMString autocomplete; textArea->GetAutocomplete(autocomplete); if (autocomplete.AsAString().EqualsLiteral("off")) { continue; } nsAutoString id; textArea->GetId(id); if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) { continue; } nsString value; textArea->GetValue(value); // In order to reduce XPath generation (which is slow), we only save data // for form fields that have been changed. (cf. bug 537289) if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, eCaseMatters)) { continue; } size += AppendEntry(textArea, id, TextField{value}, aFormData); } return size; } static uint32_t CollectInputElement(Document* aDocument, sessionstore::FormData& aFormData) { uint32_t size = 0; RefPtr inputlist = NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"input"_ns); uint32_t length = inputlist->Length(); for (uint32_t i = 0; i < length; ++i) { MOZ_ASSERT(inputlist->Item(i), "null item in node list!"); nsCOMPtr formControl = do_QueryInterface(inputlist->Item(i)); if (formControl) { auto controlType = formControl->ControlType(); if (controlType == FormControlType::InputPassword || controlType == FormControlType::InputHidden || controlType == FormControlType::InputButton || controlType == FormControlType::InputImage || controlType == FormControlType::InputSubmit || controlType == FormControlType::InputReset) { continue; } } RefPtr input = HTMLInputElement::FromNodeOrNull(inputlist->Item(i)); if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) { continue; } nsAutoString id; input->GetId(id); if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) { continue; } Nullable aInfo; input->GetAutocompleteInfo(aInfo); if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) { continue; } FormEntryValue value; if (input->ControlType() == FormControlType::InputCheckbox || input->ControlType() == FormControlType::InputRadio) { bool checked = input->Checked(); if (checked == input->DefaultChecked()) { continue; } size += AppendEntry(input, id, Checkbox{checked}, aFormData); } else if (input->ControlType() == FormControlType::InputFile) { IgnoredErrorResult rv; sessionstore::FileList file; input->MozGetFileNameArray(file.valueList(), rv); if (rv.Failed() || file.valueList().IsEmpty()) { continue; } size += AppendEntry(input, id, file, aFormData); } else { TextField field; input->GetValue(field.value(), CallerType::System); auto& value = field.value(); // In order to reduce XPath generation (which is slow), we only save data // for form fields that have been changed. (cf. bug 537289) // Also, don't want to collect credit card number. if (value.IsEmpty() || IsValidCCNumber(value) || input->HasBeenTypePassword() || input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, eCaseMatters)) { continue; } size += AppendEntry(input, id, field, aFormData); } } return size; } static uint32_t CollectSelectElement(Document* aDocument, sessionstore::FormData& aFormData) { uint32_t size = 0; RefPtr selectlist = NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"select"_ns); uint32_t length = selectlist->Length(); for (uint32_t i = 0; i < length; ++i) { MOZ_ASSERT(selectlist->Item(i), "null item in node list!"); RefPtr select = HTMLSelectElement::FromNodeOrNull(selectlist->Item(i)); if (!select) { continue; } nsAutoString id; select->GetId(id); if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) { continue; } AutocompleteInfo aInfo; select->GetAutocompleteInfo(aInfo); if (!aInfo.mCanAutomaticallyPersist) { continue; } if (!select->Multiple()) { HTMLOptionsCollection* options = select->GetOptions(); if (!options) { continue; } uint32_t numOptions = options->Length(); int32_t defaultIndex = 0; for (uint32_t idx = 0; idx < numOptions; idx++) { HTMLOptionElement* option = options->ItemAsOption(idx); if (option->DefaultSelected()) { defaultIndex = option->Index(); } } int32_t selectedIndex = select->SelectedIndex(); if (selectedIndex == defaultIndex || selectedIndex < 0) { continue; } DOMString selectVal; select->GetValue(selectVal); size += AppendEntry(select, id, SingleSelect{static_cast(selectedIndex), selectVal.AsAString()}, aFormData); } else { HTMLOptionsCollection* options = select->GetOptions(); if (!options) { continue; } bool hasDefaultValue = true; nsTArray selectslist; uint32_t numOptions = options->Length(); for (uint32_t idx = 0; idx < numOptions; idx++) { HTMLOptionElement* option = options->ItemAsOption(idx); bool selected = option->Selected(); hasDefaultValue = hasDefaultValue && (selected == option->DefaultSelected()); if (!selected) { continue; } option->GetValue(*selectslist.AppendElement()); } // In order to reduce XPath generation (which is slow), we only save data // for form fields that have been changed. (cf. bug 537289) if (hasDefaultValue) { continue; } size += AppendEntry(select, id, MultipleSelect{selectslist}, aFormData); } } return size; } /* static */ uint32_t SessionStoreUtils::CollectFormData(Document* aDocument, sessionstore::FormData& aFormData) { MOZ_DIAGNOSTIC_ASSERT(aDocument); uint32_t size = 0; size += CollectTextAreaElement(aDocument, aFormData); size += CollectInputElement(aDocument, aFormData); size += CollectSelectElement(aDocument, aFormData); aFormData.hasData() = !aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty(); return size; } /* static */ template void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument, uint16_t& aGeneratedCount, ArgsT&&... args) { RefPtr textlist = NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"textarea"_ns); uint32_t length = textlist->Length(true); for (uint32_t i = 0; i < length; ++i) { MOZ_ASSERT(textlist->Item(i), "null item in node list!"); HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i)); if (!textArea) { continue; } DOMString autocomplete; textArea->GetAutocomplete(autocomplete); if (autocomplete.AsAString().EqualsLiteral("off")) { continue; } nsAutoString id; textArea->GetId(id); if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) { continue; } nsString value; textArea->GetValue(value); // In order to reduce XPath generation (which is slow), we only save data // for form fields that have been changed. (cf. bug 537289) if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, eCaseMatters)) { continue; } AppendValueToCollectedData(textArea, id, value, aGeneratedCount, std::forward(args)...); } } /* static */ template void SessionStoreUtils::CollectFromInputElement(Document& aDocument, uint16_t& aGeneratedCount, ArgsT&&... args) { RefPtr inputlist = NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"input"_ns); uint32_t length = inputlist->Length(true); for (uint32_t i = 0; i < length; ++i) { MOZ_ASSERT(inputlist->Item(i), "null item in node list!"); nsCOMPtr formControl = do_QueryInterface(inputlist->Item(i)); if (formControl) { auto controlType = formControl->ControlType(); if (controlType == FormControlType::InputPassword || controlType == FormControlType::InputHidden || controlType == FormControlType::InputButton || controlType == FormControlType::InputImage || controlType == FormControlType::InputSubmit || controlType == FormControlType::InputReset) { continue; } } RefPtr input = HTMLInputElement::FromNodeOrNull(inputlist->Item(i)); if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) { continue; } nsAutoString id; input->GetId(id); if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) { continue; } Nullable aInfo; input->GetAutocompleteInfo(aInfo); if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) { continue; } if (input->ControlType() == FormControlType::InputCheckbox || input->ControlType() == FormControlType::InputRadio) { bool checked = input->Checked(); if (checked == input->DefaultChecked()) { continue; } AppendValueToCollectedData(input, id, checked, aGeneratedCount, std::forward(args)...); } else if (input->ControlType() == FormControlType::InputFile) { IgnoredErrorResult rv; nsTArray result; input->MozGetFileNameArray(result, rv); if (rv.Failed() || result.Length() == 0) { continue; } AppendValueToCollectedData(input, id, u"file"_ns, result, aGeneratedCount, std::forward(args)...); } else { nsString value; input->GetValue(value, CallerType::System); // In order to reduce XPath generation (which is slow), we only save data // for form fields that have been changed. (cf. bug 537289) // Also, don't want to collect credit card number. if (value.IsEmpty() || IsValidCCNumber(value) || input->HasBeenTypePassword() || input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, eCaseMatters)) { continue; } AppendValueToCollectedData(aDocument, input, id, value, aGeneratedCount, std::forward(args)...); } } } /* static */ template void SessionStoreUtils::CollectFromSelectElement(Document& aDocument, uint16_t& aGeneratedCount, ArgsT&&... args) { RefPtr selectlist = NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"select"_ns); uint32_t length = selectlist->Length(true); for (uint32_t i = 0; i < length; ++i) { MOZ_ASSERT(selectlist->Item(i), "null item in node list!"); RefPtr select = HTMLSelectElement::FromNodeOrNull(selectlist->Item(i)); if (!select) { continue; } nsAutoString id; select->GetId(id); if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) { continue; } AutocompleteInfo aInfo; select->GetAutocompleteInfo(aInfo); if (!aInfo.mCanAutomaticallyPersist) { continue; } nsAutoCString value; if (!select->Multiple()) { // s with the multiple attribute are easier to determine the // default value since each