зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 3 changesets (bug 1556358) for causing bustages in ElementInternals.cpp CLOSED TREE
Backed out changeset 0ebda393786b (bug 1556358) Backed out changeset c41794eef66a (bug 1556358) Backed out changeset a07e3e226569 (bug 1556358)
This commit is contained in:
Родитель
9a18396870
Коммит
c9a78e6659
|
@ -3332,16 +3332,8 @@ 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,
|
||||
{},
|
||||
options
|
||||
);
|
||||
return Cu.cloneInto(this._windows[aWindow.__SSi]._closedTabs, {});
|
||||
}
|
||||
|
||||
if (!DyingWindowCache.has(aWindow)) {
|
||||
|
@ -3352,7 +3344,7 @@ var SessionStoreInternal = {
|
|||
}
|
||||
|
||||
let data = DyingWindowCache.get(aWindow);
|
||||
return Cu.cloneInto(data._closedTabs, {}, options);
|
||||
return Cu.cloneInto(data._closedTabs, {});
|
||||
},
|
||||
|
||||
undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
|
||||
|
@ -6194,13 +6186,6 @@ 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,7 +269,6 @@ 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]
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
/* 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,7 +6,6 @@
|
|||
|
||||
#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"
|
||||
|
@ -389,21 +388,3 @@ 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,7 +21,6 @@ class ErrorResult;
|
|||
|
||||
namespace dom {
|
||||
|
||||
class CustomElementFormValue;
|
||||
class HTMLFormElement;
|
||||
class GlobalObject;
|
||||
|
||||
|
@ -128,17 +127,18 @@ class FormData final : public nsISupports,
|
|||
virtual nsresult AddNameDirectoryPair(const nsAString& aName,
|
||||
Directory* aDirectory) override;
|
||||
|
||||
using FormDataEntryCallback =
|
||||
bool (*)(const nsString& aName,
|
||||
const OwningBlobOrDirectoryOrUSVString& aValue, void* aClosure);
|
||||
|
||||
uint32_t Length() const { return mFormData.Length(); }
|
||||
|
||||
// Stops iteration and returns false if any invocation of callback returns
|
||||
// false. Returns true otherwise.
|
||||
// Accepts callbacks of the form `bool(const nsString&, const
|
||||
// OwningBlobOrDirectoryOrUSVString&)`.
|
||||
template <typename F>
|
||||
bool ForEach(F&& aCallback) {
|
||||
bool ForEach(FormDataEntryCallback aFunc, void* aClosure) {
|
||||
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
|
||||
FormDataTuple& tuple = mFormData[i];
|
||||
if (!aCallback(tuple.name, tuple.value)) {
|
||||
if (!aFunc(tuple.name, tuple.value, aClosure)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -154,8 +154,6 @@ class FormData final : public nsISupports,
|
|||
|
||||
Element* GetSubmitterElement() const { return mSubmitter.get(); }
|
||||
|
||||
CustomElementFormValue ConvertToCustomElementFormValue();
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsISupports> mOwner;
|
||||
|
||||
|
|
|
@ -896,36 +896,52 @@ bool WriteFormData(JSStructuredCloneWriter* aWriter, FormData* aFormData,
|
|||
return false;
|
||||
}
|
||||
|
||||
auto write = [aWriter, aHolder](
|
||||
const nsString& aName,
|
||||
const OwningBlobOrDirectoryOrUSVString& aValue) {
|
||||
if (!StructuredCloneHolder::WriteString(aWriter, aName)) {
|
||||
return false;
|
||||
}
|
||||
class MOZ_STACK_CLASS Closure final {
|
||||
JSStructuredCloneWriter* mWriter;
|
||||
StructuredCloneHolder* mHolder;
|
||||
|
||||
if (aValue.IsBlob()) {
|
||||
if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB,
|
||||
aHolder->BlobImpls().Length())) {
|
||||
public:
|
||||
Closure(JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder)
|
||||
: mWriter(aWriter), mHolder(aHolder) {}
|
||||
|
||||
static bool Write(const nsString& aName,
|
||||
const OwningBlobOrDirectoryOrUSVString& aValue,
|
||||
void* aClosure) {
|
||||
Closure* closure = static_cast<Closure*>(aClosure);
|
||||
if (!StructuredCloneHolder::WriteString(closure->mWriter, aName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<BlobImpl> blobImpl = aValue.GetAsBlob()->Impl();
|
||||
if (aValue.IsBlob()) {
|
||||
if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB,
|
||||
closure->mHolder->BlobImpls().Length())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<BlobImpl> blobImpl = aValue.GetAsBlob()->Impl();
|
||||
|
||||
closure->mHolder->BlobImpls().AppendElement(blobImpl);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aValue.IsDirectory()) {
|
||||
Directory* directory = aValue.GetAsDirectory();
|
||||
return WriteDirectory(closure->mWriter, directory);
|
||||
}
|
||||
|
||||
size_t charSize = sizeof(nsString::char_type);
|
||||
if (!JS_WriteUint32Pair(closure->mWriter, 0,
|
||||
aValue.GetAsUSVString().Length()) ||
|
||||
!JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(),
|
||||
aValue.GetAsUSVString().Length() * charSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aHolder->BlobImpls().AppendElement(blobImpl);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aValue.IsDirectory()) {
|
||||
Directory* directory = aValue.GetAsDirectory();
|
||||
return WriteDirectory(aWriter, directory);
|
||||
}
|
||||
|
||||
const size_t charSize = sizeof(nsString::char_type);
|
||||
return JS_WriteUint32Pair(aWriter, 0, aValue.GetAsUSVString().Length()) &&
|
||||
JS_WriteBytes(aWriter, aValue.GetAsUSVString().get(),
|
||||
aValue.GetAsUSVString().Length() * charSize);
|
||||
};
|
||||
return aFormData->ForEach(write);
|
||||
Closure closure(aWriter, aHolder);
|
||||
return aFormData->ForEach(Closure::Write, &closure);
|
||||
}
|
||||
|
||||
JSObject* ReadWasmModule(JSContext* aCx, uint32_t aIndex,
|
||||
|
|
|
@ -150,7 +150,6 @@
|
|||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/CustomElementRegistry.h"
|
||||
#include "mozilla/dom/CustomElementRegistryBinding.h"
|
||||
#include "mozilla/dom/CustomElementTypes.h"
|
||||
#include "mozilla/dom/DOMArena.h"
|
||||
#include "mozilla/dom/DOMException.h"
|
||||
#include "mozilla/dom/DOMExceptionBinding.h"
|
||||
|
@ -169,10 +168,8 @@
|
|||
#include "mozilla/dom/FileBlobImpl.h"
|
||||
#include "mozilla/dom/FileSystemSecurity.h"
|
||||
#include "mozilla/dom/FilteredNodeIterator.h"
|
||||
#include "mozilla/dom/FormData.h"
|
||||
#include "mozilla/dom/FragmentOrElement.h"
|
||||
#include "mozilla/dom/FromParser.h"
|
||||
#include "mozilla/dom/HTMLElement.h"
|
||||
#include "mozilla/dom/HTMLFormElement.h"
|
||||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "mozilla/dom/HTMLTextAreaElement.h"
|
||||
|
@ -9924,78 +9921,6 @@ void nsContentUtils::EnqueueLifecycleCallback(
|
|||
aDefinition);
|
||||
}
|
||||
|
||||
/* static */
|
||||
CustomElementFormValue nsContentUtils::ConvertToCustomElementFormValue(
|
||||
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 (state.IsUSVString()) {
|
||||
return state.GetAsUSVString();
|
||||
}
|
||||
return state.GetAsFormData()->ConvertToCustomElementFormValue();
|
||||
}
|
||||
|
||||
/* static */
|
||||
Nullable<OwningFileOrUSVStringOrFormData>
|
||||
nsContentUtils::ExtractFormAssociatedCustomElementValue(
|
||||
nsIGlobalObject* aGlobal,
|
||||
const mozilla::dom::CustomElementFormValue& aCEValue) {
|
||||
MOZ_ASSERT(aGlobal);
|
||||
|
||||
OwningFileOrUSVStringOrFormData value;
|
||||
switch (aCEValue.type()) {
|
||||
case CustomElementFormValue::TBlobImpl: {
|
||||
RefPtr<File> file = File::Create(aGlobal, aCEValue.get_BlobImpl());
|
||||
if (NS_WARN_IF(!file)) {
|
||||
return {};
|
||||
}
|
||||
value.SetAsFile() = file;
|
||||
} break;
|
||||
|
||||
case CustomElementFormValue::TnsString:
|
||||
value.SetAsUSVString() = aCEValue.get_nsString();
|
||||
break;
|
||||
|
||||
case CustomElementFormValue::TArrayOfFormDataTuple: {
|
||||
const auto& array = aCEValue.get_ArrayOfFormDataTuple();
|
||||
auto formData = MakeRefPtr<FormData>();
|
||||
|
||||
for (auto i = 0ul; i < array.Length(); ++i) {
|
||||
const auto& item = array.ElementAt(i);
|
||||
switch (item.value().type()) {
|
||||
case FormDataValue::TnsString:
|
||||
formData->AddNameValuePair(item.name(),
|
||||
item.value().get_nsString());
|
||||
break;
|
||||
|
||||
case FormDataValue::TBlobImpl: {
|
||||
auto blobImpl = item.value().get_BlobImpl();
|
||||
auto* blob = Blob::Create(aGlobal, blobImpl);
|
||||
formData->AddNameBlobPair(item.name(), blob);
|
||||
} break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
value.SetAsFormData() = formData;
|
||||
} break;
|
||||
case CustomElementFormValue::Tvoid_t:
|
||||
return {};
|
||||
default:
|
||||
NS_WARNING("Invalid CustomElementContentData type!");
|
||||
return {};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void nsContentUtils::AppendDocumentLevelNativeAnonymousContentTo(
|
||||
Document* aDocument, nsTArray<nsIContent*>& aElements) {
|
||||
|
|
|
@ -174,7 +174,6 @@ class ContentChild;
|
|||
class ContentFrameMessageManager;
|
||||
class ContentParent;
|
||||
struct CustomElementDefinition;
|
||||
class CustomElementFormValue;
|
||||
class CustomElementRegistry;
|
||||
class DataTransfer;
|
||||
class Document;
|
||||
|
@ -183,7 +182,6 @@ class DOMArena;
|
|||
class Element;
|
||||
class Event;
|
||||
class EventTarget;
|
||||
class HTMLElement;
|
||||
class HTMLInputElement;
|
||||
class IPCTransferable;
|
||||
class IPCTransferableData;
|
||||
|
@ -192,7 +190,6 @@ class IPCTransferableDataItem;
|
|||
struct LifecycleCallbackArgs;
|
||||
class MessageBroadcaster;
|
||||
class NodeInfo;
|
||||
class OwningFileOrUSVStringOrFormData;
|
||||
class Selection;
|
||||
struct StructuredSerializeOptions;
|
||||
class WorkerPrivate;
|
||||
|
@ -3052,15 +3049,6 @@ class nsContentUtils {
|
|||
const mozilla::dom::LifecycleCallbackArgs& aArgs,
|
||||
mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
|
||||
|
||||
static mozilla::dom::CustomElementFormValue ConvertToCustomElementFormValue(
|
||||
const mozilla::dom::Nullable<
|
||||
mozilla::dom::OwningFileOrUSVStringOrFormData>& aState);
|
||||
|
||||
static mozilla::dom::Nullable<mozilla::dom::OwningFileOrUSVStringOrFormData>
|
||||
ExtractFormAssociatedCustomElementValue(
|
||||
nsIGlobalObject* aGlobal,
|
||||
const mozilla::dom::CustomElementFormValue& aCEValue);
|
||||
|
||||
/**
|
||||
* Appends all "document level" native anonymous content subtree roots for
|
||||
* aDocument to aElements. Document level NAC subtrees are those created
|
||||
|
|
|
@ -455,20 +455,14 @@ class nsIContent : public nsINode {
|
|||
*/
|
||||
static inline bool RequiresDoneCreatingElement(int32_t aNamespace,
|
||||
nsAtom* aName) {
|
||||
if (aNamespace == kNameSpaceID_XHTML) {
|
||||
if (aName == nsGkAtoms::input || aName == nsGkAtoms::button ||
|
||||
aName == nsGkAtoms::audio || aName == nsGkAtoms::video) {
|
||||
MOZ_ASSERT(!RequiresDoneAddingChildren(aNamespace, aName),
|
||||
"Both DoneCreatingElement and DoneAddingChildren on a "
|
||||
"same element isn't supported.");
|
||||
return true;
|
||||
}
|
||||
if (aName->IsDynamic()) {
|
||||
// This could be a form-associated custom element, so check if its
|
||||
// name includes a -.
|
||||
nsDependentString name(aName->GetUTF16String());
|
||||
return name.Contains('-');
|
||||
}
|
||||
if (aNamespace == kNameSpaceID_XHTML &&
|
||||
(aName == nsGkAtoms::input || aName == nsGkAtoms::button ||
|
||||
aName == nsGkAtoms::audio || aName == nsGkAtoms::video)) {
|
||||
MOZ_ASSERT(
|
||||
!RequiresDoneAddingChildren(aNamespace, aName),
|
||||
"Both DoneCreatingElement and DoneAddingChildren on a same element "
|
||||
"isn't supported.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -144,15 +144,7 @@ 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
|
||||
|
|
|
@ -49,8 +49,7 @@ ElementInternals::ElementInternals(HTMLElement* aTarget)
|
|||
: nsIFormControl(FormControlType::FormAssociatedCustomElement),
|
||||
mTarget(aTarget),
|
||||
mForm(nullptr),
|
||||
mFieldSet(nullptr),
|
||||
mControlNumber(-1) {}
|
||||
mFieldSet(nullptr) {}
|
||||
|
||||
nsISupports* ElementInternals::GetParentObject() { return ToSupports(mTarget); }
|
||||
|
||||
|
@ -458,26 +457,4 @@ DocGroup* ElementInternals::GetDocGroup() {
|
|||
return mTarget->OwnerDoc()->GetDocGroup();
|
||||
}
|
||||
|
||||
void ElementInternals::RestoreFormValue(
|
||||
Nullable<OwningFileOrUSVStringOrFormData>&& aValue,
|
||||
Nullable<OwningFileOrUSVStringOrFormData>&& aState) {
|
||||
mSubmissionValue = aValue;
|
||||
mState = aState;
|
||||
|
||||
if (!mState.IsNull()) {
|
||||
nsContentUtils::EnqueueLifecycleCallback(
|
||||
ElementCallbackType::eFormStateRestore, mTarget,
|
||||
{
|
||||
.mState = mState,
|
||||
.mReason = RestoreReason::Restore,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ElementInternals::InitializeControlNumber() {
|
||||
MOZ_ASSERT(mControlNumber == -1,
|
||||
"FACE control number should only be initialized once!");
|
||||
mControlNumber = mTarget->OwnerDoc()->GetNextControlNumber();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -82,33 +82,11 @@ class ElementInternals final : public nsIFormControl,
|
|||
NS_IMETHOD Reset() override;
|
||||
NS_IMETHOD SubmitNamesValues(mozilla::dom::FormData* aFormData) override;
|
||||
bool AllowDrop() override { return true; }
|
||||
int32_t GetParserInsertedControlNumberForStateKey() const override {
|
||||
return mControlNumber;
|
||||
}
|
||||
|
||||
void SetFieldSet(mozilla::dom::HTMLFieldSetElement* aFieldSet) {
|
||||
mFieldSet = aFieldSet;
|
||||
}
|
||||
|
||||
const Nullable<OwningFileOrUSVStringOrFormData>& GetFormSubmissionValue()
|
||||
const {
|
||||
return mSubmissionValue;
|
||||
}
|
||||
|
||||
const Nullable<OwningFileOrUSVStringOrFormData>& GetFormState() const {
|
||||
return mState;
|
||||
}
|
||||
|
||||
void RestoreFormValue(Nullable<OwningFileOrUSVStringOrFormData>&& aValue,
|
||||
Nullable<OwningFileOrUSVStringOrFormData>&& aState);
|
||||
|
||||
const nsCString& GetStateKey() const { return mStateKey; }
|
||||
void SetStateKey(nsCString&& key) {
|
||||
MOZ_ASSERT(mStateKey.IsEmpty(), "FACE state key should only be set once!");
|
||||
mStateKey = key;
|
||||
}
|
||||
void InitializeControlNumber();
|
||||
|
||||
void UpdateFormOwner();
|
||||
void UpdateBarredFromConstraintValidation();
|
||||
|
||||
|
@ -188,8 +166,8 @@ class ElementInternals final : public nsIFormControl,
|
|||
Nullable<OwningFileOrUSVStringOrFormData> mSubmissionValue;
|
||||
|
||||
// https://html.spec.whatwg.org/#face-state
|
||||
// TODO: Bug 1734841 - Figure out how to support autocomplete for
|
||||
// form-associated custom element.
|
||||
// TODO: Bug 1734841 - Figure out how to support form restoration or
|
||||
// autocomplete for form-associated custom element
|
||||
Nullable<OwningFileOrUSVStringOrFormData> mState;
|
||||
|
||||
// https://html.spec.whatwg.org/#face-validation-message
|
||||
|
@ -199,15 +177,6 @@ class ElementInternals final : public nsIFormControl,
|
|||
RefPtr<nsGenericHTMLElement> mValidationAnchor;
|
||||
|
||||
AttrArray mAttrs;
|
||||
|
||||
// Used to store the key to a form-associated custom element in the current
|
||||
// session. Is empty until element has been upgraded.
|
||||
nsCString mStateKey;
|
||||
|
||||
// A number for a form-associated custom element that is unique within its
|
||||
// owner document. This is only set to a number for elements inserted into the
|
||||
// document by the parser from the network. Otherwise, it is -1.
|
||||
int32_t mControlNumber;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -6,27 +6,18 @@
|
|||
|
||||
#include "mozilla/dom/HTMLElement.h"
|
||||
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/PresState.h"
|
||||
#include "mozilla/dom/CustomElementRegistry.h"
|
||||
#include "mozilla/dom/ElementInternalsBinding.h"
|
||||
#include "mozilla/dom/FormData.h"
|
||||
#include "mozilla/dom/HTMLElementBinding.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsILayoutHistoryState.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
HTMLElement::HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
|
||||
FromParser aFromParser)
|
||||
HTMLElement::HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
|
||||
: nsGenericHTMLFormElement(std::move(aNodeInfo)) {
|
||||
if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
|
||||
AddStatesSilently(ElementState::HAS_DIR_ATTR_LIKE_AUTO);
|
||||
}
|
||||
if (aFromParser & FROM_PARSER_NETWORK) {
|
||||
SetFlags(HTML_ELEMENT_FROM_PARSER_NETWORK);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLElement, nsGenericHTMLFormElement)
|
||||
|
@ -82,89 +73,6 @@ void HTMLElement::UnbindFromTree(bool aNullParent) {
|
|||
UpdateBarredFromConstraintValidation();
|
||||
}
|
||||
|
||||
void HTMLElement::DoneCreatingElement() {
|
||||
if (MOZ_UNLIKELY(IsFormAssociatedElement())) {
|
||||
MaybeRestoreFormAssociatedCustomElementState();
|
||||
}
|
||||
}
|
||||
|
||||
void HTMLElement::SaveState() {
|
||||
if (MOZ_LIKELY(!IsFormAssociatedElement())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* internals = GetElementInternals();
|
||||
|
||||
nsCString stateKey = internals->GetStateKey();
|
||||
if (stateKey.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the pres state for this key, if it doesn't exist, create one.
|
||||
PresState* result = history->GetState(stateKey);
|
||||
if (!result) {
|
||||
UniquePtr<PresState> newState = NewPresState();
|
||||
result = newState.get();
|
||||
history->AddState(stateKey, std::move(newState));
|
||||
}
|
||||
|
||||
const auto& state = internals->GetFormState();
|
||||
const auto& value = internals->GetFormSubmissionValue();
|
||||
result->contentData() = CustomElementTuple(
|
||||
nsContentUtils::ConvertToCustomElementFormValue(value),
|
||||
nsContentUtils::ConvertToCustomElementFormValue(state));
|
||||
}
|
||||
|
||||
void HTMLElement::MaybeRestoreFormAssociatedCustomElementState() {
|
||||
MOZ_ASSERT(IsFormAssociatedElement());
|
||||
|
||||
auto* internals = GetElementInternals();
|
||||
if (internals->GetStateKey().IsEmpty()) {
|
||||
Document* doc = GetUncomposedDoc();
|
||||
nsCString stateKey;
|
||||
nsContentUtils::GenerateStateKey(this, doc, stateKey);
|
||||
internals->SetStateKey(std::move(stateKey));
|
||||
|
||||
RestoreFormAssociatedCustomElementState();
|
||||
}
|
||||
}
|
||||
|
||||
void HTMLElement::RestoreFormAssociatedCustomElementState() {
|
||||
MOZ_ASSERT(IsFormAssociatedElement());
|
||||
|
||||
auto* internals = GetElementInternals();
|
||||
|
||||
const nsCString& stateKey = internals->GetStateKey();
|
||||
if (stateKey.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(true);
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
PresState* result = history->GetState(stateKey);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
auto& content = result->contentData();
|
||||
if (content.type() != PresContentData::TCustomElementTuple) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& ce = content.get_CustomElementTuple();
|
||||
nsCOMPtr<nsIGlobalObject> global = GetOwnerDocument()->GetOwnerGlobal();
|
||||
internals->RestoreFormValue(
|
||||
nsContentUtils::ExtractFormAssociatedCustomElementValue(global,
|
||||
ce.value()),
|
||||
nsContentUtils::ExtractFormAssociatedCustomElementValue(global,
|
||||
ce.state()));
|
||||
}
|
||||
|
||||
void HTMLElement::SetCustomElementDefinition(
|
||||
CustomElementDefinition* aDefinition) {
|
||||
nsGenericHTMLFormElement::SetCustomElementDefinition(aDefinition);
|
||||
|
@ -176,15 +84,13 @@ void HTMLElement::SetCustomElementDefinition(
|
|||
aDefinition->mFormAssociated) {
|
||||
CustomElementData* data = GetCustomElementData();
|
||||
MOZ_ASSERT(data);
|
||||
auto* internals = data->GetOrCreateElementInternals(this);
|
||||
data->GetOrCreateElementInternals(this);
|
||||
|
||||
// This is for the case that script constructs a custom element directly,
|
||||
// e.g. via new MyCustomElement(), where the upgrade steps won't be ran to
|
||||
// update the disabled state in UpdateFormOwner().
|
||||
if (data->mState == CustomElementData::State::eCustom) {
|
||||
UpdateDisabledState(true);
|
||||
} else if (HasFlag(HTML_ELEMENT_FROM_PARSER_NETWORK)) {
|
||||
internals->InitializeControlNumber();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -292,8 +198,6 @@ void HTMLElement::UpdateFormOwner() {
|
|||
UpdateFieldSet(true);
|
||||
UpdateDisabledState(true);
|
||||
UpdateBarredFromConstraintValidation();
|
||||
|
||||
MaybeRestoreFormAssociatedCustomElementState();
|
||||
}
|
||||
|
||||
bool HTMLElement::IsDisabledForEvents(WidgetEvent* aEvent) {
|
||||
|
@ -434,7 +338,7 @@ nsGenericHTMLElement* NS_NewHTMLElement(
|
|||
mozilla::dom::FromParser aFromParser) {
|
||||
RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
|
||||
auto* nim = nodeInfo->NodeInfoManager();
|
||||
return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget(), aFromParser);
|
||||
return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget());
|
||||
}
|
||||
|
||||
// Distinct from the above in order to have function pointer that compared
|
||||
|
@ -444,5 +348,5 @@ nsGenericHTMLElement* NS_NewCustomElement(
|
|||
mozilla::dom::FromParser aFromParser) {
|
||||
RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
|
||||
auto* nim = nodeInfo->NodeInfoManager();
|
||||
return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget(), aFromParser);
|
||||
return new (nim) mozilla::dom::HTMLElement(nodeInfo.forget());
|
||||
}
|
||||
|
|
|
@ -13,8 +13,7 @@ namespace mozilla::dom {
|
|||
|
||||
class HTMLElement final : public nsGenericHTMLFormElement {
|
||||
public:
|
||||
explicit HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
|
||||
FromParser aFromParser = NOT_FROM_PARSER);
|
||||
explicit HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
@ -31,7 +30,6 @@ class HTMLElement final : public nsGenericHTMLFormElement {
|
|||
// nsIContent
|
||||
nsresult BindToTree(BindContext&, nsINode& aParent) override;
|
||||
void UnbindFromTree(bool aNullParent = true) override;
|
||||
void DoneCreatingElement() override;
|
||||
|
||||
// Element
|
||||
void SetCustomElementDefinition(
|
||||
|
@ -48,12 +46,9 @@ class HTMLElement final : public nsGenericHTMLFormElement {
|
|||
bool IsFormAssociatedElement() const override;
|
||||
void AfterClearForm(bool aUnbindOrDelete) override;
|
||||
void FieldSetDisabledChanged(bool aNotify) override;
|
||||
void SaveState() override;
|
||||
|
||||
void UpdateFormOwner();
|
||||
|
||||
void MaybeRestoreFormAssociatedCustomElementState();
|
||||
|
||||
protected:
|
||||
virtual ~HTMLElement() = default;
|
||||
|
||||
|
@ -80,9 +75,6 @@ class HTMLElement final : public nsGenericHTMLFormElement {
|
|||
void UpdateBarredFromConstraintValidation();
|
||||
|
||||
ElementInternals* GetElementInternals() const;
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||
void RestoreFormAssociatedCustomElementState();
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -1833,9 +1833,6 @@ nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
|
|||
}
|
||||
|
||||
void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) {
|
||||
// Save state before doing anything else.
|
||||
SaveState();
|
||||
|
||||
if (IsFormAssociatedElement()) {
|
||||
if (HTMLFormElement* form = GetFormInternal()) {
|
||||
// Might need to unset form
|
||||
|
@ -2243,12 +2240,6 @@ void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) {
|
|||
UpdateDisabledState(aNotify);
|
||||
}
|
||||
|
||||
void nsGenericHTMLFormElement::SaveSubtreeState() {
|
||||
SaveState();
|
||||
|
||||
nsGenericHTMLElement::SaveSubtreeState();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void nsGenericHTMLElement::Click(CallerType aCallerType) {
|
||||
|
@ -2582,6 +2573,12 @@ nsINode* nsGenericHTMLFormControlElement::GetScopeChainParent() const {
|
|||
return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent();
|
||||
}
|
||||
|
||||
void nsGenericHTMLFormControlElement::SaveSubtreeState() {
|
||||
SaveState();
|
||||
|
||||
nsGenericHTMLFormElement::SaveSubtreeState();
|
||||
}
|
||||
|
||||
nsIContent::IMEState nsGenericHTMLFormControlElement::GetDesiredIMEState() {
|
||||
TextEditor* textEditor = GetTextEditorInternal();
|
||||
if (!textEditor) {
|
||||
|
@ -2595,6 +2592,12 @@ nsIContent::IMEState nsGenericHTMLFormControlElement::GetDesiredIMEState() {
|
|||
return state;
|
||||
}
|
||||
|
||||
void nsGenericHTMLFormControlElement::UnbindFromTree(bool aNullParent) {
|
||||
// Save state before doing anything
|
||||
SaveState();
|
||||
nsGenericHTMLFormElement::UnbindFromTree(aNullParent);
|
||||
}
|
||||
|
||||
void nsGenericHTMLFormControlElement::GetAutocapitalize(
|
||||
nsAString& aValue) const {
|
||||
if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
|
||||
|
@ -2959,7 +2962,7 @@ PresState* nsGenericHTMLFormControlElementWithState::GetPrimaryPresState() {
|
|||
}
|
||||
|
||||
already_AddRefed<nsILayoutHistoryState>
|
||||
nsGenericHTMLFormElement::GetLayoutHistory(bool aRead) {
|
||||
nsGenericHTMLFormControlElementWithState::GetLayoutHistory(bool aRead) {
|
||||
nsCOMPtr<Document> doc = GetUncomposedDoc();
|
||||
if (!doc) {
|
||||
return nullptr;
|
||||
|
|
|
@ -980,11 +980,10 @@ class HTMLFieldSetElement;
|
|||
enum {
|
||||
// Used to handle keyboard activation.
|
||||
HTML_ELEMENT_ACTIVE_FOR_KEYBOARD = HTML_ELEMENT_FLAG_BIT(0),
|
||||
HTML_ELEMENT_FROM_PARSER_NETWORK = HTML_ELEMENT_FLAG_BIT(1),
|
||||
|
||||
// Remaining bits are type specific.
|
||||
HTML_ELEMENT_TYPE_SPECIFIC_BITS_OFFSET =
|
||||
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2,
|
||||
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 1,
|
||||
};
|
||||
|
||||
ASSERT_NODE_FLAGS_SPACE(HTML_ELEMENT_TYPE_SPECIFIC_BITS_OFFSET);
|
||||
|
@ -1028,7 +1027,6 @@ class nsGenericHTMLFormElement : public nsGenericHTMLElement {
|
|||
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
|
||||
|
||||
// nsIContent
|
||||
void SaveSubtreeState() override;
|
||||
nsresult BindToTree(BindContext&, nsINode& aParent) override;
|
||||
void UnbindFromTree(bool aNullParent = true) override;
|
||||
|
||||
|
@ -1056,15 +1054,6 @@ class nsGenericHTMLFormElement : public nsGenericHTMLElement {
|
|||
|
||||
void ClearForm(bool aRemoveFromForm, bool aUnbindOrDelete);
|
||||
|
||||
/**
|
||||
* Get the layout history object for a particular piece of content.
|
||||
*
|
||||
* @param aRead if true, won't return a layout history state if the
|
||||
* layout history state is empty.
|
||||
* @return the history state object
|
||||
*/
|
||||
already_AddRefed<nsILayoutHistoryState> GetLayoutHistory(bool aRead);
|
||||
|
||||
protected:
|
||||
virtual ~nsGenericHTMLFormElement() = default;
|
||||
|
||||
|
@ -1158,13 +1147,6 @@ class nsGenericHTMLFormElement : public nsGenericHTMLElement {
|
|||
* See https://html.spec.whatwg.org/#form-associated-element.
|
||||
*/
|
||||
virtual bool IsFormAssociatedElement() const { return false; }
|
||||
|
||||
/**
|
||||
* Save to presentation state. The form element will determine whether it
|
||||
* has anything to save and if so, create an entry in the layout history for
|
||||
* its pres context.
|
||||
*/
|
||||
virtual void SaveState() {}
|
||||
};
|
||||
|
||||
class nsGenericHTMLFormControlElement : public nsGenericHTMLFormElement,
|
||||
|
@ -1183,7 +1165,9 @@ class nsGenericHTMLFormControlElement : public nsGenericHTMLFormElement,
|
|||
bool IsHTMLFormControlElement() const final { return true; }
|
||||
|
||||
// nsIContent
|
||||
void SaveSubtreeState() override;
|
||||
IMEState GetDesiredIMEState() override;
|
||||
void UnbindFromTree(bool aNullParent = true) override;
|
||||
|
||||
// nsGenericHTMLElement
|
||||
// autocapitalize attribute support
|
||||
|
@ -1227,6 +1211,13 @@ class nsGenericHTMLFormControlElement : public nsGenericHTMLFormElement,
|
|||
|
||||
bool IsAutocapitalizeInheriting() const;
|
||||
|
||||
/**
|
||||
* Save to presentation state. The form control will determine whether it
|
||||
* has anything to save and if so, create an entry in the layout history for
|
||||
* its pres context.
|
||||
*/
|
||||
virtual void SaveState() {}
|
||||
|
||||
nsresult SubmitDirnameDir(mozilla::dom::FormData* aFormData);
|
||||
|
||||
/** The form that contains this control */
|
||||
|
@ -1280,6 +1271,15 @@ class nsGenericHTMLFormControlElementWithState
|
|||
*/
|
||||
mozilla::PresState* GetPrimaryPresState();
|
||||
|
||||
/**
|
||||
* Get the layout history object for a particular piece of content.
|
||||
*
|
||||
* @param aRead if true, won't return a layout history state if the
|
||||
* layout history state is empty.
|
||||
* @return the history state object
|
||||
*/
|
||||
already_AddRefed<nsILayoutHistoryState> GetLayoutHistory(bool aRead);
|
||||
|
||||
/**
|
||||
* Called when we have been cloned and adopted, and the information of the
|
||||
* node has been changed.
|
||||
|
|
|
@ -101,8 +101,6 @@ skip-if = os == "android"
|
|||
[test_radio_radionodelist.html]
|
||||
[test_required_attribute.html]
|
||||
[test_restore_form_elements.html]
|
||||
[test_save_restore_custom_elements.html]
|
||||
support-files = save_restore_custom_elements_sample.html
|
||||
[test_save_restore_radio_groups.html]
|
||||
[test_select_change_event.html]
|
||||
skip-if = os == 'mac'
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<script>
|
||||
class CEBase extends HTMLElement {
|
||||
static formAssociated = true;
|
||||
constructor() {
|
||||
super();
|
||||
this.internals = this.attachInternals();
|
||||
this.state_ = undefined;
|
||||
}
|
||||
formStateRestoreCallback(state, reason) {
|
||||
if (reason == "restore") {
|
||||
this.state_ = state;
|
||||
}
|
||||
}
|
||||
set(state, value) {
|
||||
this.state_ = state;
|
||||
this.value_ = value;
|
||||
this.internals.setFormValue(value, state);
|
||||
}
|
||||
get state() {
|
||||
return this.state_;
|
||||
}
|
||||
get value() {
|
||||
return this.value_;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("c-e", class extends CEBase {});
|
||||
</script>
|
||||
<form>
|
||||
<c-e id="custom0"></c-e>
|
||||
<c-e id="custom1"></c-e>
|
||||
<c-e id="custom2"></c-e>
|
||||
<c-e id="custom3"></c-e>
|
||||
<c-e id="custom4"></c-e>
|
||||
<upgraded-ce id="upgraded0"></upgraded-ce>
|
||||
<upgraded-ce id="upgraded1"></upgraded-ce>
|
||||
<upgraded-ce id="upgraded2"></upgraded-ce>
|
||||
<upgraded-ce id="upgraded3"></upgraded-ce>
|
||||
<upgraded-ce id="upgraded4"></upgraded-ce>
|
||||
</form>
|
||||
<script>
|
||||
customElements.define("upgraded-ce", class extends CEBase {});
|
||||
</script>
|
|
@ -1,90 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1556358
|
||||
-->
|
||||
|
||||
<head>
|
||||
<title>Test for Bug 1556358</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1556358">Mozilla Bug 1556358</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<iframe src="save_restore_custom_elements_sample.html"></iframe>
|
||||
</div>
|
||||
<script type="application/javascript">
|
||||
/** Test for Bug 1556358 **/
|
||||
|
||||
function formDataWith(...entries) {
|
||||
const formData = new FormData();
|
||||
for (let [key, value] of entries) {
|
||||
formData.append(key, value);
|
||||
}
|
||||
return formData;
|
||||
}
|
||||
|
||||
const states = [
|
||||
"test state",
|
||||
new File(["state"], "state.txt"),
|
||||
formDataWith(["1", "state"], ["2", new Blob(["state_blob"])]),
|
||||
null,
|
||||
undefined,
|
||||
];
|
||||
const values = [
|
||||
"test value",
|
||||
new File(["value"], "value.txt"),
|
||||
formDataWith(["1", "value"], ["2", new Blob(["value_blob"])]),
|
||||
"null state",
|
||||
"both value and state",
|
||||
];
|
||||
|
||||
add_task(async () => {
|
||||
const frame = document.querySelector("iframe");
|
||||
const elementTags = ["c-e", "upgraded-ce"];
|
||||
|
||||
// Set the custom element values.
|
||||
for (const tags of elementTags) {
|
||||
[...frame.contentDocument.querySelectorAll(tags)]
|
||||
.forEach((e, i) => {
|
||||
e.set(states[i], values[i]);
|
||||
});
|
||||
}
|
||||
|
||||
await new Promise(resolve => {
|
||||
frame.addEventListener("load", resolve);
|
||||
frame.contentWindow.location.reload();
|
||||
});
|
||||
|
||||
for (const tag of elementTags) {
|
||||
// Retrieve the restored values.
|
||||
const ceStates =
|
||||
[...frame.contentDocument.querySelectorAll(tag)].map((e) => e.state);
|
||||
is(ceStates.length, 5, "Should have 5 custom element states");
|
||||
|
||||
const [restored, original] = [ceStates, states];
|
||||
is(restored[0], original[0], "Value should be restored");
|
||||
|
||||
const file = restored[1];
|
||||
isnot(file, original[1], "Restored file object differs from original object.");
|
||||
is(file.name, original[1].name, "File name should be restored");
|
||||
is(await file.text(), await original[1].text(), "File text should be restored");
|
||||
|
||||
const formData = restored[2];
|
||||
isnot(formData, original[2], "Restored formdata object differs from original object.");
|
||||
is(formData.get("1"), original[2].get("1"), "Form data string should be restored");
|
||||
is(await formData.get("2").text(), await original[2].get("2").text(), "Form data blob should be restored");
|
||||
|
||||
isnot(restored[3], original[3], "Null values don't get restored");
|
||||
is(restored[3], undefined, "Null values don't get restored");
|
||||
|
||||
is(restored[4], "both value and state", "Undefined state should be set to value");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,39 +0,0 @@
|
|||
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
|
||||
/* 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 "mozilla/dom/IPCBlobUtils.h";
|
||||
|
||||
[RefCounted] using class mozilla::dom::BlobImpl from "mozilla/dom/BlobImpl.h";
|
||||
using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// Types used to store form-associated custom element state.
|
||||
union FormDataValue {
|
||||
BlobImpl;
|
||||
nsString;
|
||||
};
|
||||
|
||||
struct FormDataTuple {
|
||||
nsString name;
|
||||
FormDataValue value;
|
||||
};
|
||||
|
||||
union CustomElementFormValue {
|
||||
void_t;
|
||||
nullable BlobImpl;
|
||||
nsString;
|
||||
FormDataTuple[];
|
||||
};
|
||||
|
||||
struct CustomElementTuple {
|
||||
CustomElementFormValue value;
|
||||
CustomElementFormValue state;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -167,7 +167,6 @@ PREPROCESSED_IPDL_SOURCES += [
|
|||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
"CustomElementTypes.ipdlh",
|
||||
"DOMTypes.ipdlh",
|
||||
"IPCTransferable.ipdlh",
|
||||
"MemoryReportTypes.ipdlh",
|
||||
|
|
|
@ -11,8 +11,6 @@ using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
|
|||
using struct nsPoint from "nsPoint.h";
|
||||
[RefCounted] using class mozilla::dom::BlobImpl from "mozilla/dom/BlobImpl.h";
|
||||
|
||||
include CustomElementTypes;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
struct SelectContentData {
|
||||
|
@ -42,7 +40,6 @@ union PresContentData {
|
|||
// We can need to serialize blobs in order to transmit this type, so we need
|
||||
// to handle that in a custom handler.
|
||||
FileContentData[];
|
||||
CustomElementTuple;
|
||||
};
|
||||
|
||||
struct PresState {
|
||||
|
|
|
@ -5,10 +5,7 @@
|
|||
* 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"
|
||||
|
@ -173,27 +170,6 @@ 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,7 +7,6 @@
|
|||
using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h";
|
||||
using struct nsPoint from "nsPoint.h";
|
||||
|
||||
include CustomElementTypes;
|
||||
include DOMTypes;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -41,7 +40,6 @@ union FormEntryValue {
|
|||
FileList;
|
||||
SingleSelect;
|
||||
MultipleSelect;
|
||||
CustomElementTuple;
|
||||
};
|
||||
|
||||
struct FormEntry {
|
||||
|
|
|
@ -6,22 +6,15 @@
|
|||
|
||||
#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"
|
||||
|
@ -31,10 +24,6 @@
|
|||
#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"
|
||||
|
@ -504,27 +493,6 @@ 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) {
|
||||
|
@ -551,43 +519,6 @@ 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;
|
||||
}
|
||||
|
@ -809,51 +740,6 @@ 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) {
|
||||
|
@ -862,7 +748,6 @@ 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();
|
||||
|
@ -1051,34 +936,6 @@ 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;
|
||||
|
@ -1091,9 +948,6 @@ 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()) {
|
||||
|
@ -1269,25 +1123,6 @@ 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
|
||||
|
@ -1472,20 +1307,6 @@ 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();
|
||||
}
|
||||
|
@ -1832,36 +1653,6 @@ 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,10 +94,6 @@ 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,8 +27,6 @@ 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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче