Bug 1556358 - Part 3: Restore FACE state in SessionStore. r=edgar,farre

Depends on D174115

Differential Revision: https://phabricator.services.mozilla.com/D179278
This commit is contained in:
Adam Vandolder 2023-06-16 15:02:11 +00:00
parent 401c77f4ac
commit 61ab1463b6
14 changed files with 481 additions and 46 deletions

View File

@ -3425,8 +3425,16 @@ var SessionStoreInternal = {
},
getClosedTabDataForWindow: function ssi_getClosedTabDataForWindow(aWindow) {
// We need to enable wrapping reflectors in order to allow the cloning of
// objects containing FormDatas, which could be stored by
// form-associated custom elements.
let options = { wrapReflectors: true };
if ("__SSi" in aWindow) {
return Cu.cloneInto(this._windows[aWindow.__SSi]._closedTabs, {});
return Cu.cloneInto(
this._windows[aWindow.__SSi]._closedTabs,
{},
options
);
}
if (!DyingWindowCache.has(aWindow)) {
@ -3437,7 +3445,7 @@ var SessionStoreInternal = {
}
let data = DyingWindowCache.get(aWindow);
return Cu.cloneInto(data._closedTabs, {});
return Cu.cloneInto(data._closedTabs, {}, options);
},
undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
@ -6291,6 +6299,13 @@ var SessionStoreInternal = {
);
break;
}
if (
value.hasOwnProperty("value") &&
value.hasOwnProperty("state")
) {
root.addCustomElement(isXpath, key, value.value, value.state);
break;
}
if (
key === "sessionData" &&
["about:sessionrestore", "about:welcomeback"].includes(

View File

@ -269,6 +269,7 @@ https_first_disabled = true
skip-if =
verify && debug
[browser_formdata_cc.js]
[browser_formdata_face.js]
[browser_formdata_format.js]
skip-if = !debug && (os == "linux") # Bug 1535645
[browser_formdata_max_size.js]

View File

@ -0,0 +1,168 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* This test ensures that collecting form data for form-associated custom
* elements works as expected.
*/
add_task(async function test_face_restore() {
const URL = `data:text/html;charset=utf-8,<!DOCTYPE html>
<h1>mozilla</h1>
<script>
restoredStates = {};
customElements.define("c-e", class extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
}
formStateRestoreCallback(state, reason) {
if (reason == "restore") {
restoredStates[this.id] = state;
}
}
});
</script>
<form>
<c-e id="test1"></c-e>
<c-e id="test2"></c-e>
<c-e id="test3"></c-e>
<c-e id="test4"></c-e>
<c-e id="test5"></c-e>
<c-e id="test6"></c-e>
</form>`;
// Load a tab with a FACE.
let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, URL));
let browser = tab.linkedBrowser;
await promiseBrowserLoaded(browser);
// Set the FACE state and value.
await SpecialPowers.spawn(browser, ["c-e"], selector => {
function formDataWith(...entries) {
const formData = new content.FormData();
for (let [key, value] of entries) {
formData.append(key, value);
}
return formData;
}
const states = [
"test state",
new content.File(["state"], "state.txt"),
formDataWith(["1", "state"], ["2", new content.Blob(["state_blob"])]),
null,
undefined,
null,
];
const values = [
"test value",
new content.File(["value"], "value.txt"),
formDataWith(["1", "value"], ["2", new content.Blob(["value_blob"])]),
"null state",
"both value and state",
null,
];
[...content.document.querySelectorAll(selector)].forEach((node, i) => {
node.internals.setFormValue(values[i], states[i]);
});
});
// Close and restore the tab.
await promiseRemoveTabAndSessionState(tab);
{
let [
{
state: { formdata },
},
] = ss.getClosedTabDataForWindow(window);
is(formdata.id.test1.value, "test value", "String value should be stored");
is(formdata.id.test1.state, "test state", "String state should be stored");
let storedFile = formdata.id.test2.value;
is(storedFile.name, "value.txt", "File value name should be stored");
is(await storedFile.text(), "value", "File value text should be stored");
storedFile = formdata.id.test2.state;
is(storedFile.name, "state.txt", "File state name should be stored");
is(await storedFile.text(), "state", "File state text should be stored");
let storedFormData = formdata.id.test3.value;
is(
storedFormData.get("1"),
"value",
"FormData value string should be stored"
);
is(
await storedFormData.get("2").text(),
"value_blob",
"Form value blob should be stored"
);
storedFormData = formdata.id.test3.state;
is(
storedFormData.get("1"),
"state",
"FormData state string should be stored"
);
is(
await storedFormData.get("2").text(),
"state_blob",
"Form state blob should be stored"
);
is(formdata.id.test4.state, null, "Null state stored");
is(formdata.id.test4.value, "null state", "Value with null state stored");
is(
formdata.id.test5.value,
"both value and state",
"Undefined state should be set to value"
);
is(
formdata.id.test5.state,
"both value and state",
"Undefined state should be set to value"
);
ok(
!("test6" in formdata.id),
"Completely null values should not be stored"
);
}
tab = ss.undoCloseTab(window, 0);
browser = tab.linkedBrowser;
await promiseTabRestored(tab);
// Check that the FACE state was restored.
await SpecialPowers.spawn(browser, ["restoredStates"], async prop => {
let restoredStates = content.wrappedJSObject[prop];
is(restoredStates.test1, "test state", "String should be stored");
let storedFile = restoredStates.test2;
is(storedFile.name, "state.txt", "File name should be stored");
is(await storedFile.text(), "state", "File text should be stored");
const storedFormData = restoredStates.test3;
is(storedFormData.get("1"), "state", "Form data string should be stored");
is(
await storedFormData.get("2").text(),
"state_blob",
"Form data blob should be stored"
);
ok(!("test4" in restoredStates), "Null values don't get restored");
is(
restoredStates.test5,
"both value and state",
"Undefined state should be set to value"
);
});
// Cleanup.
gBrowser.removeTab(tab);
});

View File

@ -6,6 +6,7 @@
#include "FormData.h"
#include "nsIInputStream.h"
#include "mozilla/dom/CustomElementTypes.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/HTMLFormElement.h"
@ -388,3 +389,21 @@ nsresult FormData::CopySubmissionDataTo(
return NS_OK;
}
CustomElementFormValue FormData::ConvertToCustomElementFormValue() {
nsTArray<mozilla::dom::FormDataTuple> formValue;
ForEach([&formValue](const nsString& aName,
const OwningBlobOrDirectoryOrUSVString& aValue) -> bool {
if (aValue.IsBlob()) {
FormDataValue value(WrapNotNull(aValue.GetAsBlob()->Impl()));
formValue.AppendElement(mozilla::dom::FormDataTuple(aName, value));
} else if (aValue.IsUSVString()) {
formValue.AppendElement(
mozilla::dom::FormDataTuple(aName, aValue.GetAsUSVString()));
} else {
MOZ_ASSERT_UNREACHABLE("Can't save FormData entry Directory value!");
}
return true;
});
return formValue;
}

View File

@ -21,6 +21,7 @@ class ErrorResult;
namespace dom {
class CustomElementFormValue;
class HTMLFormElement;
class GlobalObject;
@ -153,6 +154,8 @@ class FormData final : public nsISupports,
Element* GetSubmitterElement() const { return mSubmitter.get(); }
CustomElementFormValue ConvertToCustomElementFormValue();
private:
nsCOMPtr<nsISupports> mOwner;

View File

@ -9926,50 +9926,32 @@ void nsContentUtils::EnqueueLifecycleCallback(
/* static */
CustomElementFormValue nsContentUtils::ConvertToCustomElementFormValue(
const OwningFileOrUSVStringOrFormData& aState) {
if (aState.IsFile()) {
RefPtr<BlobImpl> impl = aState.GetAsFile()->Impl();
const Nullable<OwningFileOrUSVStringOrFormData>& aState) {
if (aState.IsNull()) {
return void_t{};
}
const auto& state = aState.Value();
if (state.IsFile()) {
RefPtr<BlobImpl> impl = state.GetAsFile()->Impl();
return {std::move(impl)};
}
if (aState.IsUSVString()) {
return aState.GetAsUSVString();
if (state.IsUSVString()) {
return state.GetAsUSVString();
}
nsTArray<FormDataTuple> array;
aState.GetAsFormData()->ForEach(
[&array](const nsString& aName,
const OwningBlobOrDirectoryOrUSVString& aValue) {
FormDataTuple data;
data.name() = aName;
if (aValue.IsBlob()) {
FormDataValue value(WrapNotNull(aValue.GetAsBlob()->Impl()));
data.value() = std::move(value);
} else if (aValue.IsUSVString()) {
data.value() = aValue.GetAsUSVString();
} else {
MOZ_ASSERT_UNREACHABLE("Can't save FormData entry Directory value!");
}
array.AppendElement(data);
return true;
});
return std::move(array);
return state.GetAsFormData()->ConvertToCustomElementFormValue();
}
/* static */
Nullable<OwningFileOrUSVStringOrFormData>
nsContentUtils::ExtractFormAssociatedCustomElementValue(
mozilla::dom::HTMLElement* aElement,
nsIGlobalObject* aGlobal,
const mozilla::dom::CustomElementFormValue& aCEValue) {
MOZ_ASSERT(aGlobal);
OwningFileOrUSVStringOrFormData value;
switch (aCEValue.type()) {
case CustomElementFormValue::TBlobImpl: {
nsPIDOMWindowInner* window = aElement->OwnerDoc()->GetInnerWindow();
if (!window) {
return {};
}
RefPtr<File> file =
File::Create(window->AsGlobal(), aCEValue.get_BlobImpl());
RefPtr<File> file = File::Create(aGlobal, aCEValue.get_BlobImpl());
if (NS_WARN_IF(!file)) {
return {};
}
@ -9994,8 +9976,7 @@ nsContentUtils::ExtractFormAssociatedCustomElementValue(
case FormDataValue::TBlobImpl: {
auto blobImpl = item.value().get_BlobImpl();
auto* blob = Blob::Create(
aElement->GetComposedDoc()->GetOwnerGlobal(), blobImpl);
auto* blob = Blob::Create(aGlobal, blobImpl);
formData->AddNameBlobPair(item.name(), blob);
} break;

View File

@ -3053,11 +3053,12 @@ class nsContentUtils {
mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
static mozilla::dom::CustomElementFormValue ConvertToCustomElementFormValue(
const mozilla::dom::OwningFileOrUSVStringOrFormData& aState);
const mozilla::dom::Nullable<
mozilla::dom::OwningFileOrUSVStringOrFormData>& aState);
static mozilla::dom::Nullable<mozilla::dom::OwningFileOrUSVStringOrFormData>
ExtractFormAssociatedCustomElementValue(
mozilla::dom::HTMLElement* aElement,
nsIGlobalObject* aGlobal,
const mozilla::dom::CustomElementFormValue& aCEValue);
/**

View File

@ -144,7 +144,15 @@ dictionary CollectedNonMultipleSelectValue
required DOMString value;
};
[GenerateConversionToJS, GenerateInit]
dictionary CollectedCustomElementValue
{
(File or USVString or FormData)? value = null;
(File or USVString or FormData)? state = null;
};
// object contains either a CollectedFileListValue or a CollectedNonMultipleSelectValue or Sequence<DOMString>
// or a CollectedCustomElementValue
typedef (DOMString or boolean or object) CollectedFormDataValue;
dictionary CollectedData

View File

@ -116,12 +116,8 @@ void HTMLElement::SaveState() {
const auto& state = internals->GetFormState();
const auto& value = internals->GetFormSubmissionValue();
result->contentData() = CustomElementTuple(
value.IsNull()
? CustomElementFormValue(void_t{})
: nsContentUtils::ConvertToCustomElementFormValue(value.Value()),
state.IsNull()
? CustomElementFormValue(void_t{})
: nsContentUtils::ConvertToCustomElementFormValue(state.Value()));
nsContentUtils::ConvertToCustomElementFormValue(value),
nsContentUtils::ConvertToCustomElementFormValue(state));
}
void HTMLElement::MaybeRestoreFormAssociatedCustomElementState() {
@ -161,9 +157,11 @@ void HTMLElement::RestoreFormAssociatedCustomElementState() {
}
auto& ce = content.get_CustomElementTuple();
nsCOMPtr<nsIGlobalObject> global = GetOwnerDocument()->GetOwnerGlobal();
internals->RestoreFormValue(
nsContentUtils::ExtractFormAssociatedCustomElementValue(this, ce.value()),
nsContentUtils::ExtractFormAssociatedCustomElementValue(this,
nsContentUtils::ExtractFormAssociatedCustomElementValue(global,
ce.value()),
nsContentUtils::ExtractFormAssociatedCustomElementValue(global,
ce.state()));
}

View File

@ -5,7 +5,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CustomElementTypes.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
#include "mozilla/dom/WindowContext.h"
@ -170,6 +173,27 @@ SessionStoreRestoreData::AddMultipleSelect(bool aIsXPath,
return NS_OK;
}
NS_IMETHODIMP
SessionStoreRestoreData::AddCustomElement(bool aIsXPath,
const nsAString& aIdOrXPath,
JS::Handle<JS::Value> aValue,
JS::Handle<JS::Value> aState) {
AutoJSContext cx;
Nullable<OwningFileOrUSVStringOrFormData> value;
if (!aValue.isNull() && !value.SetValue().Init(cx, aValue)) {
return NS_ERROR_FAILURE;
}
Nullable<OwningFileOrUSVStringOrFormData> state;
if (!aState.isNull() && !state.SetValue().Init(cx, aState)) {
return NS_ERROR_FAILURE;
}
AddFormEntry(aIsXPath, aIdOrXPath,
CustomElementTuple(
nsContentUtils::ConvertToCustomElementFormValue(value),
nsContentUtils::ConvertToCustomElementFormValue(state)));
return NS_OK;
}
NS_IMETHODIMP
SessionStoreRestoreData::AddChild(nsISessionStoreRestoreData* aChild,
uint32_t aIndex) {

View File

@ -7,6 +7,7 @@
using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h";
using struct nsPoint from "nsPoint.h";
include CustomElementTypes;
include DOMTypes;
namespace mozilla {
@ -40,6 +41,7 @@ union FormEntryValue {
FileList;
SingleSelect;
MultipleSelect;
CustomElementTuple;
};
struct FormEntry {

View File

@ -6,15 +6,22 @@
#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/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"
@ -24,6 +31,10 @@
#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/UnionTypes.h"
#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
#include "mozilla/dom/txIXPathContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowProxyHolder.h"
@ -493,6 +504,27 @@ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
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) {
@ -519,6 +551,43 @@ static uint32_t SizeOfFormEntry(const FormEntryValue& aValue) {
}
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;
}
@ -740,6 +809,51 @@ static uint32_t CollectSelectElement(Document* aDocument,
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) {
@ -748,6 +862,7 @@ uint32_t SessionStoreUtils::CollectFormData(Document* aDocument,
size += CollectTextAreaElement(aDocument, aFormData);
size += CollectInputElement(aDocument, aFormData);
size += CollectSelectElement(aDocument, aFormData);
size += CollectFormAssociatedCustomElement(aDocument, aFormData);
aFormData.hasData() =
!aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty();
@ -936,6 +1051,34 @@ void SessionStoreUtils::CollectFromSelectElement(Document& aDocument,
}
}
/* 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;
@ -948,6 +1091,9 @@ static void CollectCurrentFormData(JSContext* aCx, Document& aDocument,
/* 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()) {
@ -1123,6 +1269,25 @@ static void SetElementAsObject(JSContext* aCx, Element* aElement,
}
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
@ -1307,6 +1472,20 @@ void RestoreFormEntry(Element* aNode, const FormEntryValue& aValue) {
}
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();
}
@ -1653,6 +1832,36 @@ nsresult SessionStoreUtils::ConstructFormDataValues(
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;
}

View File

@ -94,6 +94,10 @@ class SessionStoreUtils {
static void CollectFromSelectElement(Document& aDocument,
uint16_t& aGeneratedCount,
ArgsT&&... args);
template <typename... ArgsT>
static void CollectFromFormAssociatedCustomElement(Document& aDocument,
uint16_t& aGeneratedCount,
ArgsT&&... args);
static void CollectFormData(const GlobalObject& aGlobal,
WindowProxyHolder& aWindow,

View File

@ -27,6 +27,8 @@ interface nsISessionStoreRestoreData : nsISupports {
in unsigned long aSelectedIndex, in AString aValue);
void addMultipleSelect(in boolean aIsXPath, in AString aIdOrXPath,
in Array<AString> aValues);
void addCustomElement(in boolean aIsXPath, in AString aIdOrXPath,
in jsval aValue, in jsval aState);
// Add a child data object to our children list.
void addChild(in nsISessionStoreRestoreData aChild, in unsigned long aIndex);