mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
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:
parent
862e2f7842
commit
ea7eaa36e4
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
|
@ -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});
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
@ -241,7 +241,6 @@ EXTRA_JS_MODULES += [
|
||||
'SelectParentHelper.jsm',
|
||||
'ServiceRequest.jsm',
|
||||
'Services.jsm',
|
||||
'sessionstore/FormData.jsm',
|
||||
'ShortcutUtils.jsm',
|
||||
'Sqlite.jsm',
|
||||
'Timer.jsm',
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user