From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../components/sessionstore/SessionStoreUtils.cpp | 1745 ++++++++++++++++++++ 1 file changed, 1745 insertions(+) create mode 100644 toolkit/components/sessionstore/SessionStoreUtils.cpp (limited to 'toolkit/components/sessionstore/SessionStoreUtils.cpp') diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp new file mode 100644 index 0000000000..eb10ca8a93 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp @@ -0,0 +1,1745 @@ +/* -*- 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