summaryrefslogtreecommitdiffstats
path: root/toolkit/components/sessionstore/SessionStoreUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/sessionstore/SessionStoreUtils.cpp')
-rw-r--r--toolkit/components/sessionstore/SessionStoreUtils.cpp1738
1 files changed, 1738 insertions, 0 deletions
diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp
new file mode 100644
index 0000000000..a1e78e19aa
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -0,0 +1,1738 @@
+/* -*- 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/BasePrincipal.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<BrowsingContext> context = outer->GetBrowsingContext();
+ return context && !context->CreatedDynamically();
+ }
+
+ RefPtr<EventListener> 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<nsIDocShell> 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<nsIDocShellTreeItem> item;
+ docShell->GetInProcessChildAt(i, getter_AddRefs(item));
+ if (!item) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<BrowsingContext> 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<nsISupports>
+SessionStoreUtils::AddDynamicFrameFilteredListener(
+ const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
+ JS::Handle<JS::Value> 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<JSObject*> obj(cx, &aListener.toObject());
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ RefPtr<EventListener> listener =
+ new EventListener(cx, obj, global, GetIncumbentGlobal());
+
+ nsCOMPtr<nsIDOMEventListener> 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<nsIDOMEventListener> 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<CollectedData>& 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>(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<Document> 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<nsString, OwningStringOrBooleanOrObject>::EntryType*
+AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
+ uint16_t& aGeneratedCount,
+ Nullable<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::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<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::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<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::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<CollectedData>& aRetVal) {
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, aValue, &jsval)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ Record<nsString, OwningStringOrBooleanOrObject>::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<CollectedData>& 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<JS::Value> jsval(aCx);
+ if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) &&
+ jsval.isObject()) {
+ Record<nsString, OwningStringOrBooleanOrObject>::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<nsString>: file and multipleSelect */
+static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
+ const nsAString& aValueType,
+ nsTArray<nsString>& aValue,
+ uint16_t& aGeneratedCount,
+ JSContext* aCx,
+ Nullable<CollectedData>& aRetVal) {
+ JS::Rooted<JS::Value> 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<nsString, OwningStringOrBooleanOrObject>::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<nsContentList> 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<nsContentList> 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<nsIFormControl> 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<HTMLInputElement> 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<AutocompleteInfo> 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<nsContentList> 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<HTMLSelectElement> 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<uint32_t>(selectedIndex),
+ selectVal.AsAString()},
+ aFormData);
+ } else {
+ HTMLOptionsCollection* options = select->GetOptions();
+ if (!options) {
+ continue;
+ }
+ bool hasDefaultValue = true;
+ nsTArray<nsString> 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 <typename... ArgsT>
+void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args) {
+ RefPtr<nsContentList> 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<ArgsT>(args)...);
+ }
+}
+
+/* static */
+template <typename... ArgsT>
+void SessionStoreUtils::CollectFromInputElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args) {
+ RefPtr<nsContentList> 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<nsIFormControl> 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<HTMLInputElement> input =
+ HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
+ if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
+ continue;
+ }
+ nsAutoString id;
+ input->GetId(id);
+ if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
+ continue;
+ }
+ Nullable<AutocompleteInfo> 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<ArgsT>(args)...);
+ } else if (input->ControlType() == FormControlType::InputFile) {
+ IgnoredErrorResult rv;
+ nsTArray<nsString> result;
+ input->MozGetFileNameArray(result, rv);
+ if (rv.Failed() || result.Length() == 0) {
+ continue;
+ }
+ AppendValueToCollectedData(input, id, u"file"_ns, result, aGeneratedCount,
+ std::forward<ArgsT>(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<ArgsT>(args)...);
+ }
+ }
+}
+
+/* static */
+template <typename... ArgsT>
+void SessionStoreUtils::CollectFromSelectElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args) {
+ RefPtr<nsContentList> 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<HTMLSelectElement> 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()) {
+ // <select>s without the multiple attribute are hard to determine the
+ // default value, so assume we don't have the default.
+ DOMString selectVal;
+ select->GetValue(selectVal);
+ CollectedNonMultipleSelectValue val;
+ val.mSelectedIndex = select->SelectedIndex();
+ val.mValue = selectVal.AsAString();
+ AppendValueToCollectedData(select, id, val, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ } else {
+ // <select>s with the multiple attribute are easier to determine the
+ // default value since each <option> has a defaultSelected property
+ HTMLOptionsCollection* options = select->GetOptions();
+ if (!options) {
+ continue;
+ }
+ bool hasDefaultValue = true;
+ nsTArray<nsString> selectslist;
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ bool selected = option->Selected();
+ if (!selected) {
+ continue;
+ }
+ option->GetValue(*selectslist.AppendElement());
+ hasDefaultValue =
+ hasDefaultValue && (selected == option->DefaultSelected());
+ }
+ // 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;
+ }
+
+ AppendValueToCollectedData(select, id, u"multipleSelect"_ns, selectslist,
+ aGeneratedCount, std::forward<ArgsT>(args)...);
+ }
+ }
+}
+
+static void CollectCurrentFormData(JSContext* aCx, Document& aDocument,
+ Nullable<CollectedData>& aRetVal) {
+ uint16_t generatedCount = 0;
+ /* textarea element */
+ SessionStoreUtils::CollectFromTextAreaElement(aDocument, generatedCount,
+ aRetVal);
+ /* input element */
+ SessionStoreUtils::CollectFromInputElement(aDocument, generatedCount, aCx,
+ aRetVal);
+ /* select element */
+ SessionStoreUtils::CollectFromSelectElement(aDocument, generatedCount, aCx,
+ aRetVal);
+
+ Element* bodyElement = aDocument.GetBody();
+ if (bodyElement && bodyElement->IsInDesignMode()) {
+ bodyElement->GetInnerHTML(aRetVal.SetValue().mInnerHTML.Construct(),
+ IgnoreErrors());
+ }
+
+ if (aRetVal.IsNull()) {
+ return;
+ }
+
+ // Store the frame's current URL with its form data so that we can compare
+ // it when restoring data to not inject form data into the wrong document.
+ nsIURI* uri = aDocument.GetDocumentURI();
+ if (uri) {
+ uri->GetSpecIgnoringRef(aRetVal.SetValue().mUrl.Construct());
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsString(Element* aElement, const nsAString& aValue) {
+ IgnoredErrorResult rv;
+ if (auto* textArea = HTMLTextAreaElement::FromNode(aElement)) {
+ textArea->SetValue(aValue, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ return;
+ }
+ if (auto* input = HTMLInputElement::FromNode(aElement)) {
+ input->SetValue(aValue, CallerType::NonSystem, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ return;
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsBool(Element* aElement, bool aValue) {
+ HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
+ if (input) {
+ bool checked = input->Checked();
+ if (aValue != checked) {
+ input->SetChecked(aValue);
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsFiles(HTMLInputElement* aElement,
+ const CollectedFileListValue& aValue) {
+ IgnoredErrorResult rv;
+ aElement->MozSetFileNameArray(aValue.mFileList, rv);
+ if (rv.Failed()) {
+ return;
+ }
+ nsContentUtils::DispatchInputEvent(aElement);
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsSelect(HTMLSelectElement* aElement,
+ const CollectedNonMultipleSelectValue& aValue) {
+ HTMLOptionsCollection* options = aElement->GetOptions();
+ if (!options) {
+ return;
+ }
+ int32_t selectIdx = options->SelectedIndex();
+ if (selectIdx >= 0) {
+ nsAutoString selectOptionVal;
+ options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
+ if (aValue.mValue.Equals(selectOptionVal)) {
+ return;
+ }
+ }
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ nsAutoString optionValue;
+ option->GetValue(optionValue);
+ if (aValue.mValue.Equals(optionValue)) {
+ aElement->SetSelectedIndex(idx);
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
+ const nsTArray<nsString>& aValueArray) {
+ bool fireEvent = false;
+ HTMLOptionsCollection* options = aElement->GetOptions();
+ if (!options) {
+ return;
+ }
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ nsAutoString optionValue;
+ option->GetValue(optionValue);
+ for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
+ if (optionValue.Equals(aValueArray[i])) {
+ option->SetSelected(true);
+ if (!option->DefaultSelected()) {
+ fireEvent = true;
+ }
+ }
+ }
+ }
+ if (fireEvent) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsObject(JSContext* aCx, Element* aElement,
+ JS::Handle<JS::Value> aObject) {
+ RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aElement);
+ if (input) {
+ if (input->ControlType() == FormControlType::InputFile) {
+ CollectedFileListValue value;
+ if (value.Init(aCx, aObject)) {
+ SetElementAsFiles(input, value);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+ return;
+ }
+ RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aElement);
+ if (select) {
+ // For Single Select Element
+ if (!select->Multiple()) {
+ CollectedNonMultipleSelectValue value;
+ if (value.Init(aCx, aObject)) {
+ SetElementAsSelect(select, value);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ return;
+ }
+
+ // For Multiple Selects Element
+ bool isArray = false;
+ JS::IsArrayObject(aCx, aObject, &isArray);
+ if (!isArray) {
+ return;
+ }
+ JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
+ uint32_t arrayLength = 0;
+ if (!JS::GetArrayLength(aCx, arrayObj, &arrayLength)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ nsTArray<nsString> array(arrayLength);
+ for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
+ JS::Rooted<JS::Value> element(aCx);
+ if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ if (!element.isString()) {
+ return;
+ }
+ nsAutoJSString value;
+ if (!value.init(aCx, element)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ array.AppendElement(value);
+ }
+ SetElementAsMultiSelect(select, array);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetSessionData(JSContext* aCx, Element* aElement,
+ JS::MutableHandle<JS::Value> aObject) {
+ nsAutoString data;
+ if (nsContentUtils::StringifyJSON(aCx, aObject, data,
+ UndefinedIsNullStringLiteral)) {
+ SetElementAsString(aElement, data);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetInnerHTML(Document& aDocument, const nsString& aInnerHTML) {
+ RefPtr<Element> bodyElement = aDocument.GetBody();
+ if (bodyElement && bodyElement->IsInDesignMode()) {
+ IgnoredErrorResult rv;
+ bodyElement->SetInnerHTML(aInnerHTML, aDocument.NodePrincipal(), rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(bodyElement);
+ }
+ }
+}
+
+class FormDataParseContext : public txIParseContext {
+ public:
+ explicit FormDataParseContext(bool aCaseInsensitive)
+ : mIsCaseInsensitive(aCaseInsensitive) {}
+
+ nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
+ if (aPrefix == nsGkAtoms::xul) {
+ aID = kNameSpaceID_XUL;
+ } else {
+ MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
+ aID = kNameSpaceID_XHTML;
+ }
+ return NS_OK;
+ }
+
+ nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) override {
+ return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
+ }
+
+ bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
+
+ void SetErrorOffset(uint32_t aOffset) override {}
+
+ private:
+ bool mIsCaseInsensitive;
+};
+
+static Element* FindNodeByXPath(Document& aDocument,
+ const nsAString& aExpression) {
+ FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
+ IgnoredErrorResult rv;
+ UniquePtr<XPathExpression> expression(
+ aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
+ &aDocument, rv));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<XPathResult> result = expression->Evaluate(
+ aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
+}
+
+/* static */
+bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
+ Document& aDocument,
+ const CollectedData& aData) {
+ if (!aData.mUrl.WasPassed()) {
+ return true;
+ }
+
+ // Don't restore any data for the given frame if the URL
+ // stored in the form data doesn't match its current URL.
+ nsAutoCString url;
+ Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
+ if (!aData.mUrl.Value().Equals(url)) {
+ return false;
+ }
+
+ using Change = SessionStoreChangeListener::Change;
+ SessionStoreChangeListener::CollectSessionStoreData(
+ aDocument.GetWindowContext(), EnumSet<Change>(Change::Input));
+
+ if (aData.mInnerHTML.WasPassed()) {
+ SetInnerHTML(aDocument, aData.mInnerHTML.Value());
+ }
+ if (aData.mId.WasPassed()) {
+ for (auto& entry : aData.mId.Value().Entries()) {
+ RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
+ if (node == nullptr) {
+ continue;
+ }
+ if (entry.mValue.IsString()) {
+ SetElementAsString(node, entry.mValue.GetAsString());
+ } else if (entry.mValue.IsBoolean()) {
+ SetElementAsBool(node, entry.mValue.GetAsBoolean());
+ } else {
+ // For about:{sessionrestore,welcomeback} we saved the field as JSON to
+ // avoid nested instances causing humongous sessionstore.js files.
+ // cf. bug 467409
+ JSContext* cx = aGlobal.Context();
+ if (entry.mKey.EqualsLiteral("sessionData")) {
+ if (url.EqualsLiteral("about:sessionrestore") ||
+ url.EqualsLiteral("about:welcomeback")) {
+ JS::Rooted<JS::Value> object(
+ cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetSessionData(cx, node, &object);
+ continue;
+ }
+ }
+ JS::Rooted<JS::Value> object(
+ cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetElementAsObject(cx, node, object);
+ }
+ }
+ }
+
+ if (aData.mXpath.WasPassed()) {
+ for (auto& entry : aData.mXpath.Value().Entries()) {
+ RefPtr<Element> node = FindNodeByXPath(aDocument, entry.mKey);
+ if (node == nullptr) {
+ continue;
+ }
+ if (entry.mValue.IsString()) {
+ SetElementAsString(node, entry.mValue.GetAsString());
+ } else if (entry.mValue.IsBoolean()) {
+ SetElementAsBool(node, entry.mValue.GetAsBoolean());
+ } else {
+ JS::Rooted<JS::Value> object(
+ aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetElementAsObject(aGlobal.Context(), node, object);
+ }
+ }
+ }
+
+ return true;
+}
+
+MOZ_CAN_RUN_SCRIPT
+void RestoreFormEntry(Element* aNode, const FormEntryValue& aValue) {
+ using Type = sessionstore::FormEntryValue::Type;
+ switch (aValue.type()) {
+ case Type::TCheckbox:
+ SetElementAsBool(aNode, aValue.get_Checkbox().value());
+ break;
+ case Type::TTextField:
+ SetElementAsString(aNode, aValue.get_TextField().value());
+ break;
+ case Type::TFileList: {
+ if (RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aNode);
+ input && input->ControlType() == FormControlType::InputFile) {
+ CollectedFileListValue value;
+ value.mFileList = aValue.get_FileList().valueList().Clone();
+ SetElementAsFiles(input, value);
+ }
+ break;
+ }
+ case Type::TSingleSelect: {
+ if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
+ select && !select->Multiple()) {
+ CollectedNonMultipleSelectValue value;
+ value.mSelectedIndex = aValue.get_SingleSelect().index();
+ value.mValue = aValue.get_SingleSelect().value();
+ SetElementAsSelect(select, value);
+ }
+ break;
+ }
+ case Type::TMultipleSelect: {
+ if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
+ select && select->Multiple()) {
+ SetElementAsMultiSelect(select,
+ aValue.get_MultipleSelect().valueList());
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+}
+
+/* static */
+void SessionStoreUtils::RestoreFormData(
+ Document& aDocument, const nsString& aInnerHTML,
+ const nsTArray<SessionStoreRestoreData::Entry>& aEntries) {
+ using Change = SessionStoreChangeListener::Change;
+ SessionStoreChangeListener::CollectSessionStoreData(
+ aDocument.GetWindowContext(), EnumSet<Change>(Change::Input));
+
+ if (!aInnerHTML.IsEmpty()) {
+ SetInnerHTML(aDocument, aInnerHTML);
+ }
+
+ for (const auto& entry : aEntries) {
+ RefPtr<Element> node = entry.mIsXPath
+ ? FindNodeByXPath(aDocument, entry.mData.id())
+ : aDocument.GetElementById(entry.mData.id());
+ if (node) {
+ RestoreFormEntry(node, entry.mData.value());
+ }
+ }
+}
+
+typedef void (*CollectorFunc)(JSContext* aCx, Document& aDocument,
+ Nullable<CollectedData>& aRetVal);
+
+/**
+ * A function that will recursively call |CollectorFunc| to collect data for all
+ * non-dynamic frames in the current frame/docShell tree.
+ */
+static void CollectFrameTreeData(JSContext* aCx,
+ BrowsingContext* aBrowsingContext,
+ Nullable<CollectedData>& aRetVal,
+ CollectorFunc aFunc) {
+ if (aBrowsingContext->CreatedDynamically()) {
+ return;
+ }
+
+ nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
+ if (!window || !window->GetDocShell()) {
+ return;
+ }
+
+ Document* document = window->GetExtantDoc();
+ if (!document) {
+ return;
+ }
+
+ /* Collect data from current frame */
+ aFunc(aCx, *document, aRetVal);
+
+ /* Collect data from all child frame */
+ nsTArray<JSObject*> childrenData;
+ SequenceRooter<JSObject*> rooter(aCx, &childrenData);
+ uint32_t trailingNullCounter = 0;
+
+ // This is not going to work for fission. Bug 1572084 for tracking it.
+ for (auto& child : aBrowsingContext->Children()) {
+ NullableRootedDictionary<CollectedData> data(aCx);
+ CollectFrameTreeData(aCx, child, data, aFunc);
+ if (data.IsNull()) {
+ childrenData.AppendElement(nullptr);
+ trailingNullCounter++;
+ continue;
+ }
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, data.SetValue(), &jsval)) {
+ JS_ClearPendingException(aCx);
+ continue;
+ }
+ childrenData.AppendElement(&jsval.toObject());
+ trailingNullCounter = 0;
+ }
+
+ if (trailingNullCounter != childrenData.Length()) {
+ childrenData.TruncateLength(childrenData.Length() - trailingNullCounter);
+ aRetVal.SetValue().mChildren.Construct() = std::move(childrenData);
+ }
+}
+
+/* static */ void SessionStoreUtils::CollectScrollPosition(
+ const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal) {
+ CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
+ CollectCurrentScrollPosition);
+}
+
+/* static */ void SessionStoreUtils::CollectFormData(
+ const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal) {
+ CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
+ CollectCurrentFormData);
+}
+
+/* static */ void SessionStoreUtils::ComposeInputData(
+ const nsTArray<CollectedInputDataValue>& aData, InputElementData& ret) {
+ nsTArray<int> selectedIndex, valueIdx;
+ nsTArray<nsString> id, selectVal, strVal, type;
+ nsTArray<bool> boolVal;
+
+ for (const CollectedInputDataValue& data : aData) {
+ id.AppendElement(data.id);
+ type.AppendElement(data.type);
+
+ if (data.value.is<mozilla::dom::CollectedNonMultipleSelectValue>()) {
+ valueIdx.AppendElement(selectVal.Length());
+ selectedIndex.AppendElement(
+ data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
+ .mSelectedIndex);
+ selectVal.AppendElement(
+ data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
+ .mValue);
+ } else if (data.value.is<CopyableTArray<nsString>>()) {
+ // The first valueIdx is "index of the first string value"
+ valueIdx.AppendElement(strVal.Length());
+ strVal.AppendElements(data.value.as<CopyableTArray<nsString>>());
+ // The second valueIdx is "index of the last string value" + 1
+ id.AppendElement(data.id);
+ type.AppendElement(data.type);
+ valueIdx.AppendElement(strVal.Length());
+ } else if (data.value.is<nsString>()) {
+ valueIdx.AppendElement(strVal.Length());
+ strVal.AppendElement(data.value.as<nsString>());
+ } else if (data.type.EqualsLiteral("bool")) {
+ valueIdx.AppendElement(boolVal.Length());
+ boolVal.AppendElement(data.value.as<bool>());
+ }
+ }
+
+ if (selectedIndex.Length() != 0) {
+ ret.mSelectedIndex.Construct(std::move(selectedIndex));
+ }
+ if (valueIdx.Length() != 0) {
+ ret.mValueIdx.Construct(std::move(valueIdx));
+ }
+ if (id.Length() != 0) {
+ ret.mId.Construct(std::move(id));
+ }
+ if (selectVal.Length() != 0) {
+ ret.mSelectVal.Construct(std::move(selectVal));
+ }
+ if (strVal.Length() != 0) {
+ ret.mStrVal.Construct(std::move(strVal));
+ }
+ if (type.Length() != 0) {
+ ret.mType.Construct(std::move(type));
+ }
+ if (boolVal.Length() != 0) {
+ ret.mBoolVal.Construct(std::move(boolVal));
+ }
+}
+
+already_AddRefed<nsISessionStoreRestoreData>
+SessionStoreUtils::ConstructSessionStoreRestoreData(
+ const GlobalObject& aGlobal) {
+ nsCOMPtr<nsISessionStoreRestoreData> data = new SessionStoreRestoreData();
+ return data.forget();
+}
+
+/* static */
+already_AddRefed<Promise> SessionStoreUtils::InitializeRestore(
+ const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
+ nsISessionStoreRestoreData* aData, ErrorResult& aError) {
+ if (!mozilla::SessionHistoryInParent()) {
+ MOZ_CRASH("why were we called?");
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aContext.IsTop());
+
+ MOZ_DIAGNOSTIC_ASSERT(aData);
+ nsCOMPtr<SessionStoreRestoreData> data = do_QueryInterface(aData);
+ aContext.SetRestoreData(data, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISHistory> shistory = aContext.GetSessionHistory();
+ MOZ_DIAGNOSTIC_ASSERT(shistory);
+ shistory->ReloadCurrentEntry();
+
+ return aContext.GetRestorePromise();
+}
+
+/* static */
+void SessionStoreUtils::RestoreDocShellState(
+ nsIDocShell* aDocShell, const DocShellRestoreState& aState) {
+ if (aDocShell) {
+ if (aState.URI()) {
+ aDocShell->SetCurrentURIForSessionStore(aState.URI());
+ }
+ RestoreDocShellCapabilities(aDocShell, aState.docShellCaps());
+ }
+}
+
+/* static */
+already_AddRefed<Promise> SessionStoreUtils::RestoreDocShellState(
+ const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
+ const nsACString& aURL, const nsCString& aDocShellCaps,
+ ErrorResult& aError) {
+ MOZ_RELEASE_ASSERT(mozilla::SessionHistoryInParent());
+ MOZ_RELEASE_ASSERT(aContext.IsTop());
+
+ WindowGlobalParent* wgp = aContext.GetCurrentWindowGlobal();
+ if (!wgp) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_DIAGNOSTIC_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (!aURL.IsEmpty()) {
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURL))) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ bool allowJavascript = true;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aDocShellCaps, ',').ToRange()) {
+ if (token.EqualsLiteral("Javascript")) {
+ allowJavascript = false;
+ }
+ }
+
+ Unused << aContext.SetAllowJavascript(allowJavascript);
+
+ DocShellRestoreState state = {uri, aDocShellCaps};
+
+ // TODO (anny): Investigate removing this roundtrip.
+ wgp->SendRestoreDocShellState(state)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise](void) { promise->MaybeResolveWithUndefined(); },
+ [promise](void) { promise->MaybeRejectWithUndefined(); });
+
+ return promise.forget();
+}
+
+/* static */
+void SessionStoreUtils::RestoreSessionStorageFromParent(
+ const GlobalObject& aGlobal, const CanonicalBrowsingContext& aContext,
+ const Record<nsCString, Record<nsString, nsString>>& aSessionStorage) {
+ nsTArray<SSCacheCopy> cacheInitList;
+ for (const auto& originEntry : aSessionStorage.Entries()) {
+ nsCOMPtr<nsIPrincipal> storagePrincipal =
+ BasePrincipal::CreateContentPrincipal(originEntry.mKey);
+
+ nsCString originKey;
+ nsresult rv = storagePrincipal->GetStorageOriginKey(originKey);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ SSCacheCopy& cacheInit = *cacheInitList.AppendElement();
+
+ cacheInit.originKey() = originKey;
+ PrincipalToPrincipalInfo(storagePrincipal, &cacheInit.principalInfo());
+
+ for (const auto& entry : originEntry.mValue.Entries()) {
+ SSSetItemInfo& setItemInfo = *cacheInit.data().AppendElement();
+ setItemInfo.key() = entry.mKey;
+ setItemInfo.value() = entry.mValue;
+ }
+ }
+
+ BackgroundSessionStorageManager::LoadData(aContext.Id(), cacheInitList);
+}
+
+/* static */
+nsresult SessionStoreUtils::ConstructFormDataValues(
+ JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
+ nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
+ aEntries,
+ bool aParseSessionData) {
+ using EntryType = Record<nsString, OwningStringOrBooleanOrObject>::EntryType;
+
+ if (!aEntries.SetCapacity(aValues.Length(), fallible)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& value : aValues) {
+ EntryType* entry = aEntries.AppendElement();
+
+ using Type = sessionstore::FormEntryValue::Type;
+ switch (value.value().type()) {
+ case Type::TCheckbox:
+ entry->mValue.SetAsBoolean() = value.value().get_Checkbox().value();
+ break;
+ case Type::TTextField: {
+ if (aParseSessionData && value.id() == u"sessionData"_ns) {
+ JS::Rooted<JS::Value> jsval(aCx);
+ const auto& fieldValue = value.value().get_TextField().value();
+ if (!JS_ParseJSON(aCx, fieldValue.get(), fieldValue.Length(),
+ &jsval) ||
+ !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ } else {
+ entry->mValue.SetAsString() = value.value().get_TextField().value();
+ }
+ break;
+ }
+ case Type::TFileList: {
+ CollectedFileListValue file;
+ file.mFileList = value.value().get_FileList().valueList().Clone();
+
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, file, &jsval) || !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ case Type::TSingleSelect: {
+ CollectedNonMultipleSelectValue select;
+ select.mSelectedIndex = value.value().get_SingleSelect().index();
+ select.mValue = value.value().get_SingleSelect().value();
+
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, select, &jsval) || !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ case Type::TMultipleSelect: {
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, value.value().get_MultipleSelect().valueList(),
+ &jsval) ||
+ !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ default:
+ break;
+ }
+
+ entry->mKey = value.id();
+ }
+
+ return NS_OK;
+}
+
+static nsresult ConstructSessionStorageValue(
+ const nsTArray<SSSetItemInfo>& aValues,
+ Record<nsString, nsString>& aRecord) {
+ auto& entries = aRecord.Entries();
+ for (const auto& value : aValues) {
+ auto entry = entries.AppendElement();
+ entry->mKey = value.key();
+ entry->mValue = value.value();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult SessionStoreUtils::ConstructSessionStorageValues(
+ CanonicalBrowsingContext* aBrowsingContext,
+ const nsTArray<SSCacheCopy>& aValues,
+ Record<nsCString, Record<nsString, nsString>>& aRecord) {
+ if (!aRecord.Entries().SetCapacity(aValues.Length(), fallible)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& value : aValues) {
+ auto storagePrincipal = PrincipalInfoToPrincipal(value.principalInfo());
+ if (storagePrincipal.isErr()) {
+ continue;
+ }
+
+ auto entry = aRecord.Entries().AppendElement();
+
+ if (!entry->mValue.Entries().SetCapacity(value.data().Length(), fallible)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(storagePrincipal.inspect()->GetOrigin(entry->mKey))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ConstructSessionStorageValue(value.data(), entry->mValue);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool SessionStoreUtils::CopyProperty(JSContext* aCx, JS::Handle<JSObject*> aDst,
+ JS::Handle<JSObject*> aSrc,
+ const nsAString& aName) {
+ JS::Rooted<JS::PropertyKey> name(aCx);
+ const char16_t* data;
+ size_t length = aName.GetData(&data);
+
+ if (!JS_CharsToId(aCx, JS::TwoByteChars(data, length), &name)) {
+ return false;
+ }
+
+ bool found = false;
+ if (!JS_HasPropertyById(aCx, aSrc, name, &found) || !found) {
+ return true;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetPropertyById(aCx, aSrc, name, &value)) {
+ return false;
+ }
+
+ if (value.isNullOrUndefined()) {
+ return true;
+ }
+
+ return JS_DefinePropertyById(aCx, aDst, name, value, JSPROP_ENUMERATE);
+}