fune/toolkit/components/sessionstore/SessionStoreUtils.cpp
Cristian Tuns 8e06a7a853 Backed out 12 changesets (bug 1825325, bug 1825336, bug 1825333, bug 1825332, bug 1825324, bug 1824557, bug 1825328, bug 1825335, bug 1825330, bug 1825329, bug 1825327, bug 1825331) for causing build bustages in nsClipboard.cpp CLOSED TREE
Backed out changeset 9de3ed24d3a0 (bug 1825336)
Backed out changeset aef787728f19 (bug 1825335)
Backed out changeset a04c341244c1 (bug 1825333)
Backed out changeset e3ad15f762ba (bug 1825332)
Backed out changeset eed23da92a27 (bug 1825331)
Backed out changeset 8213bb54376e (bug 1825330)
Backed out changeset 747ec5ac4994 (bug 1825329)
Backed out changeset e91ff431f92d (bug 1825328)
Backed out changeset 59c18d13768b (bug 1825327)
Backed out changeset 538096d99e49 (bug 1825325)
Backed out changeset c76eb9d9b095 (bug 1825324)
Backed out changeset 8b81410eb686 (bug 1824557)
2023-03-31 12:58:53 -04:00

1736 lines
58 KiB
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/PropertyAndElement.h" // JS_GetElement
#include "js/TypeDecls.h"
#include "jsapi.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/AutocompleteInfoBinding.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/PBackgroundSessionStorageCache.h"
#include "mozilla/dom/SessionStoreChangeListener.h"
#include "mozilla/dom/SessionStoreChild.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/txIXPathContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/dom/XPathResult.h"
#include "mozilla/dom/XPathEvaluator.h"
#include "mozilla/dom/XPathExpression.h"
#include "mozilla/dom/PBackgroundSessionStorageCache.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/UniquePtr.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentList.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsGlobalWindowOuter.h"
#include "nsIContentInlines.h"
#include "nsIDocShell.h"
#include "nsIFormControl.h"
#include "nsIScrollableFrame.h"
#include "nsISHistory.h"
#include "nsIXULRuntime.h"
#include "nsPresContext.h"
#include "nsPrintfCString.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::sessionstore;
namespace {
class DynamicFrameEventFilter final : public nsIDOMEventListener {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)
explicit DynamicFrameEventFilter(EventListener* aListener)
: mListener(aListener) {}
NS_IMETHODIMP HandleEvent(Event* aEvent) override {
if (mListener && TargetInNonDynamicDocShell(aEvent)) {
mListener->HandleEvent(*aEvent);
}
return NS_OK;
}
private:
~DynamicFrameEventFilter() = default;
bool TargetInNonDynamicDocShell(Event* aEvent) {
EventTarget* target = aEvent->GetTarget();
if (!target) {
return false;
}
nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal();
if (!outer || !outer->GetDocShell()) {
return false;
}
RefPtr<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;
}
MOZ_DIAGNOSTIC_ASSERT(aContext.GetSessionHistory());
aContext.GetSessionHistory()->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);
}