Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/toolkit/components/sessionstore/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 65 kB image not shown  

Quelle  SessionStoreUtils.cpp   Sprache: C

 
/* -*- 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/Object.h"
#include "js/PropertyAndElement.h"  // JS_GetElement
#include "js/TypeDecls.h"
#include "jsapi.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/AutocompleteInfoBinding.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/CustomElementTypes.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/FragmentOrElement.h"
#include "mozilla/dom/HTMLElement.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/SessionStoreUtilsBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TrustedHTML.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/sessionstore/SessionStoreTypes.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 "nsGlobalWindowInner.h"
#include "nsIContentInlines.h"
#include "nsIDocShell.h"
#include "nsIFormControl.h"
#include "nsISHistory.h"
#include "nsIXULRuntime.h"
#include "nsPresContext.h"
#include "nsPrintfCString.h"
#include "nsDocShell.h"
#include "nsNetUtil.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

  // 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->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("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();
}

/* For form-associated custom element state */
static void AppendValueToCollectedData(
    nsINode* aNode, const nsAString& aId,
    const Nullable<OwningFileOrUSVStringOrFormData>& aValue,
    const Nullable<OwningFileOrUSVStringOrFormData>& aState,
    uint16_t& aGeneratedCount, JSContext* aCx,
    Nullable<CollectedData>& aRetVal) {
  Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
      AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);

  CollectedCustomElementValue val;
  val.mValue = aValue;
  val.mState = aState;
  JS::Rooted<JS::Value> jsval(aCx);
  if (!ToJSValue(aCx, val, &jsval)) {
    JS_ClearPendingException(aCx);
    return;
  }
  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;
    }
    case FormEntryValue::TCustomElementTuple: {
      auto customElementTupleSize =
          [](const CustomElementFormValue& value) -> uint32_t {
        switch (value.type()) {
          case CustomElementFormValue::TBlobImpl:
            return value.get_BlobImpl()->GetAllocationSize();
          case CustomElementFormValue::TnsString:
            return value.get_nsString().Length();
          case CustomElementFormValue::TArrayOfFormDataTuple: {
            uint32_t formDataSize = 0;
            for (const auto& entry : value.get_ArrayOfFormDataTuple()) {
              formDataSize += entry.name().Length();
              const auto& entryValue = entry.value();
              switch (entryValue.type()) {
                case FormDataValue::TBlobImpl:
                  formDataSize +=
                      entryValue.get_BlobImpl()->GetAllocationSize();
                  break;
                case FormDataValue::TnsString:
                  formDataSize += entryValue.get_nsString().Length();
                  break;
                default:
                  break;
              }
            }
            return formDataSize;
          }
          default:
            return 0;
        }
      };

      auto ceTuple = aValue.get_CustomElementTuple();
      size += customElementTupleSize(ceTuple.value());
      size += customElementTupleSize(ceTuple.state());
      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!");

    auto* 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!");
    if (const auto* formControl =
            nsIFormControl::FromNodeOrNull(inputlist->Item(i))) {
      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 already_AddRefed<nsContentList> GetFormAssociatedCustomElements(
    nsINode* aRootNode) {
  MOZ_ASSERT(aRootNode, "Content list has to have a root");

  auto matchFunc = [](Element* aElement, int32_t aNamespace, nsAtom* aAtom,
                      void* aData) -> bool {
    return aElement->HasCustomElementData() &&
           aElement->GetCustomElementData()->IsFormAssociated();
  };
  RefPtr<nsContentList> list =
      new nsContentList(aRootNode, matchFunc, nullptr, nullptr);
  return list.forget();
}

static uint32_t CollectFormAssociatedCustomElement(
    Document* aDocument, sessionstore::FormData& aFormData) {
  uint32_t size = 0;
  RefPtr<nsContentList> faceList = GetFormAssociatedCustomElements(aDocument);
  uint32_t length = faceList->Length();
  for (uint32_t i = 0; i < length; ++i) {
    MOZ_ASSERT(faceList->Item(i), "null item in node list!");
    RefPtr<Element> element = Element::FromNode(faceList->Item(i));

    nsAutoString id;
    element->GetId(id);
    if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
      continue;
    }

    const auto* internals =
        element->GetCustomElementData()->GetElementInternals();
    auto formState = internals->GetFormState();
    auto formValue = internals->GetFormSubmissionValue();
    if (formState.IsNull() && formValue.IsNull()) {
      continue;
    }

    CustomElementTuple entry;
    entry.value() = nsContentUtils::ConvertToCustomElementFormValue(formValue);
    entry.state() = nsContentUtils::ConvertToCustomElementFormValue(formState);
    size += AppendEntry(element, id, entry, 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);
  size += CollectFormAssociatedCustomElement(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!");
    if (const auto* formControl =
            nsIFormControl::FromNodeOrNull(inputlist->Item(i))) {
      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 */
template <typename... ArgsT>
void SessionStoreUtils::CollectFromFormAssociatedCustomElement(
    Document& aDocument, uint16_t& aGeneratedCount, ArgsT&&... args) {
  RefPtr<nsContentList> faceList = GetFormAssociatedCustomElements(&aDocument);
  uint32_t length = faceList->Length(true);
  for (uint32_t i = 0; i < length; ++i) {
    MOZ_ASSERT(faceList->Item(i), "null item in node list!");
    RefPtr<Element> element = Element::FromNode(faceList->Item(i));

    nsAutoString id;
    element->GetId(id);
    if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
      continue;
    }

    auto* internals = element->GetCustomElementData()->GetElementInternals();
    const auto& state = internals->GetFormState();
    const auto& value = internals->GetFormSubmissionValue();
    if (state.IsNull() && value.IsNull()) {
      continue;
    }

    AppendValueToCollectedData(element, id, value, state, 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);
  /* form-associated custom element */
  SessionStoreUtils::CollectFromFormAssociatedCustomElement(
      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)) {
    // Known live because `aElement` is known live.
    MOZ_KnownLive(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);
  }

  // For Form-Associated Custom Element:
  if (!aObject.isObject()) {
    // Don't restore null values.
    return;
  }

  auto* data = aElement->GetCustomElementData();
  if (!data || !data->IsFormAssociated()) {
    return;
  }
  auto* internals = data->GetElementInternals();

  CollectedCustomElementValue value;
  if (!value.Init(aCx, aObject)) {
    JS_ClearPendingException(aCx);
    return;
  }
  internals->RestoreFormValue(std::move(value.mValue), std::move(value.mState));
}

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 nsAString& aInnerHTML) {
  RefPtr<Element> bodyElement = aDocument.GetBody();
  if (bodyElement && bodyElement->IsInDesignMode()) {
    IgnoredErrorResult rv;
    bodyElement->SetInnerHTMLTrusted(aInnerHTML, aDocument.NodePrincipal(), rv);
    if (!rv.Failed()) {
      nsContentUtils::DispatchInputEvent(bodyElement);
    }
  }
}

class FormDataParseContext : public txIParseContext {
 public:
  explicit FormDataParseContext(bool aCaseInsensitive)
      : mIsCaseInsensitive(aCaseInsensitive) {}

  int32_t resolveNamespacePrefix(nsAtom* aPrefix) override {
    if (aPrefix == nsGkAtoms::xul) {
      return kNameSpaceID_XUL;
    }

    MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
    return kNameSpaceID_XHTML;
  }

  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;
    }
    case Type::TCustomElementTuple: {
      const auto* data = aNode->GetCustomElementData();
      if (!data || !data->IsFormAssociated()) {
        return;
      }
      auto* internals = data->GetElementInternals();
      nsCOMPtr<nsIGlobalObject> global = aNode->GetOwnerGlobal();
      internals->RestoreFormValue(
          nsContentUtils::ExtractFormAssociatedCustomElementValue(
              global, aValue.get_CustomElementTuple().value()),
          nsContentUtils::ExtractFormAssociatedCustomElementValue(
              global, aValue.get_CustomElementTuple().state()));
      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) {
    nsCOMPtr<nsIURI> currentUri;
    nsDocShell::Cast(aDocShell)->GetCurrentURI(getter_AddRefs(currentUri));
    if (aState.URI() &&
        (!currentUri || mozilla::net::SchemeIsAbout(currentUri))) {
      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;
      }
      case Type::TCustomElementTuple: {
        nsCOMPtr<nsIGlobalObject> global;
        JS::Rooted<JSObject*> globalObject(aCx, JS::CurrentGlobalOrNull(aCx));
        if (NS_WARN_IF(!globalObject)) {
          break;
        }
        global = xpc::NativeGlobal(globalObject);
        if (NS_WARN_IF(!global)) {
          break;
        }

        auto formState =
            nsContentUtils::ExtractFormAssociatedCustomElementValue(
                global, value.value().get_CustomElementTuple().state());
        auto formValue =
            nsContentUtils::ExtractFormAssociatedCustomElementValue(
                global, value.value().get_CustomElementTuple().value());
        MOZ_ASSERT(!formValue.IsNull() || !formState.IsNull(),
                   "Shouldn't be storing null values!");

        CollectedCustomElementValue val;
        val.mValue = formValue;
        val.mState = formState;
        JS::Rooted<JS::Value> jsval(aCx);
        if (!ToJSValue(aCx, val, &jsval)) {
          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) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=93 G=92

¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.