mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
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:
parent
401c77f4ac
commit
61ab1463b6
@ -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(
|
||||
|
@ -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]
|
||||
|
168
browser/components/sessionstore/test/browser_formdata_face.js
Normal file
168
browser/components/sessionstore/test/browser_formdata_face.js
Normal 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);
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user