Bug 1497146 part 3 - Convert FormData.jsm to C++ [restore() part] r=peterv,mikedeboer

Differential Revision: https://phabricator.services.mozilla.com/D12672

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alphan Chen 2019-01-24 12:53:28 +00:00
parent 862e2f7842
commit ea7eaa36e4
12 changed files with 318 additions and 297 deletions

View File

@ -8,8 +8,6 @@ var EXPORTED_SYMBOLS = ["ContentRestore"];
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
ChromeUtils.defineModuleGetter(this, "FormData",
"resource://gre/modules/FormData.jsm");
ChromeUtils.defineModuleGetter(this, "SessionHistory",
"resource://gre/modules/sessionstore/SessionHistory.jsm");
ChromeUtils.defineModuleGetter(this, "SessionStorage",
@ -303,7 +301,7 @@ ContentRestoreInternal.prototype = {
// restore() will return false, and thus abort restoration for the
// current |frame| and its descendants, if |data.url| is given but
// doesn't match the loaded document's URL.
return FormData.restore(frame, data);
return SessionStoreUtils.restoreFormData(frame.document, data);
});
// Restore scroll data.

View File

@ -3564,6 +3564,8 @@ class Document : public nsINode,
void SetDocTreeHadAudibleMedia();
void SetDocTreeHadPlayRevoked();
mozilla::dom::XPathEvaluator* XPathEvaluator();
protected:
void DoUpdateSVGUseElementShadowTrees();
@ -3693,8 +3695,6 @@ class Document : public nsINode,
nsCString GetContentTypeInternal() const { return mContentType; }
mozilla::dom::XPathEvaluator* XPathEvaluator();
// Update our frame request callback scheduling state, if needed. This will
// schedule or unschedule them, if necessary, and update
// mFrameRequestCallbacksScheduled. aOldShell should only be passed when

View File

@ -111,6 +111,7 @@ namespace SessionStoreUtils {
* Form data encoded in an object.
*/
CollectedFormData collectFormData(Document document);
boolean restoreFormData(Document document, optional CollectedFormData data);
};
dictionary SSScrollPositionDict {

View File

@ -48,6 +48,9 @@ class XPathEvaluator final : public NonRefcountedDOMObject {
ErrorResult& rv);
XPathExpression* CreateExpression(const nsAString& aExpression,
nsINode* aResolver, ErrorResult& aRv);
XPathExpression* CreateExpression(const nsAString& aExpression,
txIParseContext* aContext,
Document* aDocument, ErrorResult& aRv);
nsINode* CreateNSResolver(nsINode& aNodeResolver) { return &aNodeResolver; }
already_AddRefed<XPathResult> Evaluate(
JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
@ -55,10 +58,6 @@ class XPathEvaluator final : public NonRefcountedDOMObject {
ErrorResult& rv);
private:
XPathExpression* CreateExpression(const nsAString& aExpression,
txIParseContext* aContext,
Document* aDocument, ErrorResult& aRv);
nsWeakPtr mDocument;
RefPtr<txResultRecycler> mRecycler;
};

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
'txIXPathContext.h',
'XPathEvaluator.h',
'XPathExpression.h',
'XPathResult.h',

View File

@ -6,7 +6,9 @@
#ifndef __TX_I_XPATH_CONTEXT
#define __TX_I_XPATH_CONTEXT
#include "txCore.h"
#include "nscore.h"
#include "nsISupportsImpl.h"
#include "nsStringFwd.h"
class FunctionCall;
class nsAtom;

View File

@ -7,7 +7,6 @@ ChromeUtils.import("resource://gre/modules/GeckoViewChildModule.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
FormData: "resource://gre/modules/FormData.jsm",
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
GeckoViewAutoFill: "resource://gre/modules/GeckoViewAutoFill.jsm",
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
@ -203,7 +202,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
// restore() will return false, and thus abort restoration for the
// current |frame| and its descendants, if |data.url| is given but
// doesn't match the loaded document's URL.
return FormData.restore(frame, data);
return SessionStoreUtils.restoreFormData(frame.document, data);
});
}
}, {capture: true, mozSystemGroup: true, once: true});

View File

@ -9,7 +9,6 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
EventDispatcher: "resource://gre/modules/Messaging.jsm",
FormData: "resource://gre/modules/FormData.jsm",
OS: "resource://gre/modules/osfile.jsm",
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
@ -1395,7 +1394,7 @@ SessionStore.prototype = {
// restore() will return false, and thus abort restoration for the
// current |frame| and its descendants, if |data.url| is given but
// doesn't match the loaded document's URL.
return FormData.restore(frame, data);
return SessionStoreUtils.restoreFormData(frame.document, data);
});
}
},

View File

@ -3,11 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/JSON.h"
#include "jsapi.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/txIXPathContext.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/dom/XPathResult.h"
#include "mozilla/dom/XPathEvaluator.h"
#include "mozilla/dom/XPathExpression.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentList.h"
#include "nsContentUtils.h"
@ -560,8 +565,8 @@ static void CollectFromSelectElement(JSContext* aCx, Document& aDocument,
}
bool hasDefaultValue = true;
nsTArray<nsString> selectslist;
int numOptions = options->Length();
for (int idx = 0; idx < numOptions; idx++) {
uint32_t numOptions = options->Length();
for (uint32_t idx = 0; idx < numOptions; idx++) {
HTMLOptionElement* option = options->ItemAsOption(idx);
bool selected = option->Selected();
if (!selected) {
@ -668,3 +673,297 @@ static void CollectFromXULTextbox(Document& aDocument,
uri->GetSpecIgnoringRef(aRetVal.mUrl.Construct());
}
}
MOZ_CAN_RUN_SCRIPT
static void SetElementAsString(Element* aElement, const nsAString& aValue) {
IgnoredErrorResult rv;
HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNodeOrNull(aElement);
if (textArea) {
textArea->SetValue(aValue, rv);
if (!rv.Failed()) {
nsContentUtils::DispatchInputEvent(aElement);
}
return;
}
HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(aElement);
if (input) {
input->SetValue(aValue, CallerType::NonSystem, rv);
if (!rv.Failed()) {
nsContentUtils::DispatchInputEvent(aElement);
return;
}
}
input = HTMLInputElement::FromNodeOrNull(nsFocusManager::GetRedirectedFocus(aElement));
if (input) {
input->SetValue(aValue, CallerType::NonSystem, rv);
if (!rv.Failed()) {
nsContentUtils::DispatchInputEvent(aElement);
}
}
}
MOZ_CAN_RUN_SCRIPT
static void SetElementAsBool(Element* aElement, bool aValue) {
HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(aElement);
if (input) {
bool checked = input->Checked();
if (aValue != checked) {
input->SetChecked(aValue);
nsContentUtils::DispatchInputEvent(aElement);
}
}
}
MOZ_CAN_RUN_SCRIPT
static void SetElementAsFiles(HTMLInputElement* aElement,
const CollectedFileListValue& aValue) {
nsTArray<nsString> fileList;
IgnoredErrorResult rv;
aElement->MozSetFileNameArray(aValue.mFileList, rv);
if (rv.Failed()) {
return;
}
nsContentUtils::DispatchInputEvent(aElement);
}
MOZ_CAN_RUN_SCRIPT
static void SetElementAsSelect(HTMLSelectElement* aElement,
const CollectedNonMultipleSelectValue& aValue) {
HTMLOptionsCollection* options = aElement->GetOptions();
if (!options) {
return;
}
int32_t selectIdx = options->SelectedIndex();
if (selectIdx >= 0) {
nsAutoString selectOptionVal;
options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
if (aValue.mValue.Equals(selectOptionVal)) {
return;
}
}
uint32_t numOptions = options->Length();
for (uint32_t idx = 0; idx < numOptions; idx++) {
HTMLOptionElement* option = options->ItemAsOption(idx);
nsAutoString optionValue;
option->GetValue(optionValue);
if (aValue.mValue.Equals(optionValue)) {
aElement->SetSelectedIndex(idx);
nsContentUtils::DispatchInputEvent(aElement);
}
}
}
MOZ_CAN_RUN_SCRIPT
static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
const nsTArray<nsString>& aValueArray) {
bool fireEvent = false;
HTMLOptionsCollection* options = aElement->GetOptions();
if (!options) {
return;
}
uint32_t numOptions = options->Length();
for (uint32_t idx = 0; idx < numOptions; idx++) {
HTMLOptionElement* option = options->ItemAsOption(idx);
nsAutoString optionValue;
option->GetValue(optionValue);
for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
if (optionValue.Equals(aValueArray[i])) {
option->SetSelected(true);
if (!option->DefaultSelected()) {
fireEvent = true;
}
}
}
}
if (fireEvent) {
nsContentUtils::DispatchInputEvent(aElement);
}
}
MOZ_CAN_RUN_SCRIPT
static void SetElementAsObject(JSContext* aCx, Element* aElement, JSObject* aObject) {
JS::RootedValue object(aCx, JS::ObjectValue(*aObject));
RefPtr<HTMLInputElement> input = HTMLInputElement::FromNodeOrNull(aElement);
if (input) {
if (input->ControlType() == NS_FORM_INPUT_FILE) {
CollectedFileListValue value;
if (value.Init(aCx, object)) {
SetElementAsFiles(input, value);
} else {
JS_ClearPendingException(aCx);
}
}
return;
}
RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNodeOrNull(aElement);
if (select) {
// For Single Select Element
if (!select->Multiple()) {
CollectedNonMultipleSelectValue value;
if (value.Init(aCx, object)) {
SetElementAsSelect(select, value);
} else {
JS_ClearPendingException(aCx);
}
return;
}
// For Multiple Selects Element
bool isArray = false;
JS_IsArrayObject(aCx, object, &isArray);
if (!isArray) {
return;
}
JS::Rooted<JSObject*> arrayObj(aCx, aObject);
uint32_t arrayLength = 0;
if (!JS_GetArrayLength(aCx, arrayObj, &arrayLength)) {
JS_ClearPendingException(aCx);
return;
}
nsTArray<nsString> array(arrayLength);
for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
JS::Rooted<JS::Value> element(aCx);
if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
JS_ClearPendingException(aCx);
return;
}
if (!element.isString()) {
return;
}
nsAutoJSString value;
if (!value.init(aCx, element)) {
JS_ClearPendingException(aCx);
return;
}
array.AppendElement(value);
}
SetElementAsMultiSelect(select, array);
}
}
MOZ_CAN_RUN_SCRIPT
static void SetRestoreData(JSContext* aCx, Element* aElement, JSObject* aObject) {
JS::RootedValue object(aCx, JS::ObjectValue(*aObject));
nsAutoString data;
if (nsContentUtils::StringifyJSON(aCx, &object, data)) {
SetElementAsString(aElement, data);
} else {
JS_ClearPendingException(aCx);
}
}
MOZ_CAN_RUN_SCRIPT
static void SetInnerHTML(Document& aDocument, const CollectedFormData& aData) {
RefPtr<Element> bodyElement = aDocument.GetBody();
if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
IgnoredErrorResult rv;
bodyElement->SetInnerHTML(aData.mInnerHTML.Value(),
aDocument.NodePrincipal(), rv);
if (!rv.Failed()) {
nsContentUtils::DispatchInputEvent(bodyElement);
}
}
}
class FormDataParseContext : public txIParseContext {
public:
explicit FormDataParseContext(bool aCaseInsensitive)
: mIsCaseInsensitive(aCaseInsensitive) {}
nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
if (aPrefix == nsGkAtoms::xul) {
aID = kNameSpaceID_XUL;
} else {
MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
aID = kNameSpaceID_XHTML;
}
return NS_OK;
}
nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
FunctionCall** aFunction) override {
return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
}
bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
void SetErrorOffset(uint32_t aOffset) override {}
private:
bool mIsCaseInsensitive;
};
static Element* FindNodeByXPath(JSContext* aCx, Document& aDocument,
const nsAString& aExpression) {
FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
IgnoredErrorResult rv;
nsAutoPtr<XPathExpression> expression(
aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
&aDocument, rv));
if (rv.Failed()) {
return nullptr;
}
RefPtr<XPathResult> result = expression->Evaluate(
aCx, aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
if (rv.Failed()) {
return nullptr;
}
return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY
/* static */ bool SessionStoreUtils::RestoreFormData(
const GlobalObject& aGlobal, Document& aDocument,
const CollectedFormData& aData) {
if (!aData.mUrl.WasPassed()) {
return true;
}
// Don't restore any data for the given frame if the URL
// stored in the form data doesn't match its current URL.
nsAutoCString url;
Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
if (!aData.mUrl.Value().Equals(url)) {
return false;
}
if (aData.mInnerHTML.WasPassed()) {
SetInnerHTML(aDocument, aData);
}
if (aData.mId.WasPassed()) {
for (auto& entry : aData.mId.Value().Entries()) {
RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
if (entry.mValue.IsString()) {
SetElementAsString(node, entry.mValue.GetAsString());
} else if (entry.mValue.IsBoolean()) {
SetElementAsBool(node, entry.mValue.GetAsBoolean());
} else {
// For about:{sessionrestore,welcomeback} we saved the field as JSON to
// avoid nested instances causing humongous sessionstore.js files.
// cf. bug 467409
JSContext* cx = aGlobal.Context();
if (entry.mKey.EqualsLiteral("sessionData")) {
nsAutoCString url;
Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
if (url.EqualsLiteral("about:sessionrestore") ||
url.EqualsLiteral("about:welcomeback")) {
SetRestoreData(cx, node, entry.mValue.GetAsObject());
continue;
}
}
SetElementAsObject(cx, node, entry.mValue.GetAsObject());
}
}
}
if (aData.mXpath.WasPassed()) {
for (auto& entry : aData.mXpath.Value().Entries()) {
RefPtr<Element> node = FindNodeByXPath(aGlobal.Context(), aDocument, entry.mKey);
if (entry.mValue.IsString()) {
SetElementAsString(node, entry.mValue.GetAsString());
} else if (entry.mValue.IsBoolean()) {
SetElementAsBool(node, entry.mValue.GetAsBoolean());
} else {
SetElementAsObject(aGlobal.Context(), node, entry.mValue.GetAsObject());
}
}
}
return true;
}

View File

@ -54,6 +54,10 @@ class SessionStoreUtils {
static void CollectFormData(const GlobalObject& aGlobal, Document& aDocument,
CollectedFormData& aRetVal);
MOZ_CAN_RUN_SCRIPT_BOUNDARY
static bool RestoreFormData(const GlobalObject& aGlobal, Document& aDocument,
const CollectedFormData& aData);
};
} // namespace dom

View File

@ -241,7 +241,6 @@ EXTRA_JS_MODULES += [
'SelectParentHelper.jsm',
'ServiceRequest.jsm',
'Services.jsm',
'sessionstore/FormData.jsm',
'ShortcutUtils.jsm',
'Sqlite.jsm',
'Timer.jsm',

View File

@ -1,280 +0,0 @@
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["FormData"];
/**
* Returns whether the given URL very likely has input
* fields that contain serialized session store data.
*/
function isRestorationPage(url) {
return url == "about:sessionrestore" || url == "about:welcomeback";
}
/**
* Returns whether the given form |data| object contains nested restoration
* data for a page like about:sessionrestore or about:welcomeback.
*/
function hasRestorationData(data) {
if (isRestorationPage(data.url) && data.id) {
return typeof(data.id.sessionData) == "object";
}
return false;
}
/**
* Returns the given document's current URI and strips
* off the URI's anchor part, if any.
*/
function getDocumentURI(doc) {
return doc.documentURI.replace(/#.*$/, "");
}
/**
* The public API exported by this module that allows to collect
* and restore form data for a document and its subframes.
*/
var FormData = Object.freeze({
restore(frame, data) {
return FormDataInternal.restore(frame, data);
},
restoreTree(root, data) {
FormDataInternal.restoreTree(root, data);
},
});
/**
* This module's internal API.
*/
var FormDataInternal = {
namespaceURIs: {
"xhtml": "http://www.w3.org/1999/xhtml",
"xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
},
/**
* Resolves an XPath query generated by node.generateXPath.
*/
resolve(aDocument, aQuery) {
let xptype = aDocument.defaultView.XPathResult.FIRST_ORDERED_NODE_TYPE;
return aDocument.evaluate(aQuery, aDocument, this.resolveNS.bind(this), xptype, null).singleNodeValue;
},
/**
* Namespace resolver for the above XPath resolver.
*/
resolveNS(aPrefix) {
return this.namespaceURIs[aPrefix] || null;
},
/**
* @returns an XPath query to all savable form field nodes
*/
get restorableFormNodesXPath() {
let formNodesXPath = "//textarea|//xhtml:textarea|" +
"//select|//xhtml:select|" +
"//input|//xhtml:input" +
// Special case for about:config's search field.
"|/xul:window[@id='config']//xul:textbox[@id='textbox']";
delete this.restorableFormNodesXPath;
return (this.restorableFormNodesXPath = formNodesXPath);
},
/**
* Restores form |data| for the given frame. The data is expected to be in
* the same format that FormData.collect() returns.
*
* @param frame (DOMWindow)
* The frame to restore form data to.
* @param data (object)
* An object holding form data.
*/
restore({document: doc}, data) {
if (!data.url) {
return true;
}
// Don't restore any data for the given frame if the URL
// stored in the form data doesn't match its current URL.
if (data.url != getDocumentURI(doc)) {
return false;
}
// For about:{sessionrestore,welcomeback} we saved the field as JSON to
// avoid nested instances causing humongous sessionstore.js files.
// cf. bug 467409
if (hasRestorationData(data)) {
data.id.sessionData = JSON.stringify(data.id.sessionData);
}
if ("id" in data) {
let retrieveNode = id => doc.getElementById(id);
this.restoreManyInputValues(data.id, retrieveNode);
}
if ("xpath" in data) {
let retrieveNode = xpath => this.resolve(doc, xpath);
this.restoreManyInputValues(data.xpath, retrieveNode);
}
if ("innerHTML" in data) {
if (doc.body && doc.designMode == "on") {
// eslint-disable-next-line no-unsanitized/property
doc.body.innerHTML = data.innerHTML;
this.fireInputEvent(doc.body);
}
}
return true;
},
/**
* Iterates the given form data, retrieving nodes for all the keys and
* restores their appropriate values.
*
* @param data (object)
* A subset of the form data as collected by FormData.collect(). This
* is either data stored under "id" or under "xpath".
* @param retrieve (function)
* The function used to retrieve the input field belonging to a key
* in the given |data| object.
*/
restoreManyInputValues(data, retrieve) {
for (let key of Object.keys(data)) {
let input = retrieve(key);
if (input) {
this.restoreSingleInputValue(input, data[key]);
}
}
},
/**
* Restores a given form value to a given DOMNode and takes care of firing
* the appropriate DOM event should the input's value change.
*
* @param aNode
* DOMNode to set form value on.
* @param aValue
* Value to set form element to.
*/
restoreSingleInputValue(aNode, aValue) {
let fireEvent = false;
if (typeof aValue == "string" && aNode.type != "file") {
// Don't dispatch an input event if there is no change.
if (aNode.value == aValue) {
return;
}
aNode.value = aValue;
fireEvent = true;
} else if (typeof aValue == "boolean") {
// Don't dispatch a change event for no change.
if (aNode.checked == aValue) {
return;
}
aNode.checked = aValue;
fireEvent = true;
} else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
// Don't dispatch a change event for no change
if (aNode.options[aNode.selectedIndex].value == aValue.value) {
return;
}
// find first option with matching aValue if possible
for (let i = 0; i < aNode.options.length; i++) {
if (aNode.options[i].value == aValue.value) {
aNode.selectedIndex = i;
fireEvent = true;
break;
}
}
} else if (aValue && aValue.fileList && aValue.type == "file" &&
aNode.type == "file") {
try {
// FIXME (bug 1122855): This won't work in content processes.
aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
} catch (e) {
Cu.reportError("mozSetFileNameArray: " + e);
}
fireEvent = true;
} else if (Array.isArray(aValue) && aNode.options) {
Array.forEach(aNode.options, function(opt, index) {
// don't worry about malformed options with same values
opt.selected = aValue.indexOf(opt.value) > -1;
// Only fire the event here if this wasn't selected by default
if (!opt.defaultSelected) {
fireEvent = true;
}
});
}
// Fire events for this node if applicable
if (fireEvent) {
this.fireInputEvent(aNode);
}
},
/**
* Dispatches an event of type "input" to the given |node|.
*
* @param node (DOMNode)
*/
fireInputEvent(node) {
// "inputType" value hasn't been decided for session restor:
// https://github.com/w3c/input-events/issues/30#issuecomment-438693664
let event = node.isInputEventTarget ?
new node.ownerGlobal.InputEvent("input", {bubbles: true, inputType: ""}) :
new node.ownerGlobal.Event("input", {bubbles: true});
node.dispatchEvent(event);
},
/**
* Restores form data for the current frame hierarchy starting at |root|
* using the given form |data|.
*
* If the given |root| frame's hierarchy doesn't match that of the given
* |data| object we will silently discard data for unreachable frames. For
* security reasons we will never restore form data to the wrong frames as
* we bail out silently if the stored URL doesn't match the frame's current
* URL.
*
* @param root (DOMWindow)
* @param data (object)
* {
* formdata: {id: {input1: "value1"}},
* children: [
* {formdata: {id: {input2: "value2"}}},
* null,
* {formdata: {xpath: { ... }}, children: [ ... ]}
* ]
* }
*/
restoreTree(root, data) {
// Restore data for the given |root| frame and its descendants. If restore()
// returns false this indicates the |data.url| doesn't match the loaded
// document URI. We then must ignore this branch for security reasons.
if (this.restore(root, data) === false) {
return;
}
if (!data.hasOwnProperty("children")) {
return;
}
let frames = root.frames;
for (let index of Object.keys(data.children)) {
if (index < frames.length) {
this.restoreTree(frames[index], data.children[index]);
}
}
},
};