You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

230 lines
7.4 KiB

/* 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/. */
this.EXPORTED_SYMBOLS = [ "DocumentUtils" ];
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
this.DocumentUtils = {
/**
* Obtain form data for a DOMDocument instance.
*
* The returned object has 2 keys, "id" and "xpath". Each key holds an object
* which further defines form data.
*
* The "id" object maps element IDs to values. The "xpath" object maps the
* XPath of an element to its value.
*
* @param aDocument
* DOMDocument instance to obtain form data for.
* @return object
* Form data encoded in an object.
*/
getFormData: function(aDocument) {
let formNodes = aDocument.evaluate(
XPathGenerator.restorableFormNodes,
aDocument,
XPathGenerator.resolveNS,
Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null
);
let node;
let ret = {id: {}, xpath: {}};
// Limit the number of XPath expressions for performance reasons. See
// bug 477564.
const MAX_TRAVERSED_XPATHS = 100;
let generatedCount = 0;
while (node = formNodes.iterateNext()) {
let nId = node.id;
let hasDefaultValue = true;
let value;
// Only generate a limited number of XPath expressions for perf reasons
// (cf. bug 477564)
if (!nId && generatedCount > MAX_TRAVERSED_XPATHS) {
continue;
}
if (node instanceof Ci.nsIDOMHTMLInputElement ||
node instanceof Ci.nsIDOMHTMLTextAreaElement) {
switch (node.type) {
case "checkbox":
case "radio":
value = node.checked;
hasDefaultValue = value == node.defaultChecked;
break;
case "file":
value = { type: "file", fileList: node.mozGetFileNameArray() };
hasDefaultValue = !value.fileList.length;
break;
default: // text, textarea
value = node.value;
hasDefaultValue = value == node.defaultValue;
break;
}
} else if (!node.multiple) {
// <select>s without the multiple attribute are hard to determine the
// default value, so assume we don't have the default.
hasDefaultValue = false;
value = { selectedIndex: node.selectedIndex, value: node.value };
} else {
// <select>s with the multiple attribute are easier to determine the
// default value since each <option> has a defaultSelected
let options = Array.map(node.options, function(aOpt, aIx) {
let oSelected = aOpt.selected;
hasDefaultValue = hasDefaultValue && (oSelected == aOpt.defaultSelected);
return oSelected ? aOpt.value : -1;
});
value = options.filter(function(aIx) aIx !== -1);
}
// In order to reduce XPath generation (which is slow), we only save data
// for form fields that have been changed. (cf. bug 537289)
if (!hasDefaultValue) {
if (nId) {
ret.id[nId] = value;
} else {
generatedCount++;
ret.xpath[XPathGenerator.generate(node)] = value;
}
}
}
return ret;
},
/**
* Merges form data on a document from previously obtained data.
*
* This is the inverse of getFormData(). The data argument is the same object
* type which is returned by getFormData(): an object containing the keys
* "id" and "xpath" which are each objects mapping element identifiers to
* form values.
*
* Where the document has existing form data for an element, the value
* will be replaced. Where the document has a form element but no matching
* data in the passed object, the element is untouched.
*
* @param aDocument
* DOMDocument instance to which to restore form data.
* @param aData
* Object defining form data.
*/
mergeFormData: function(aDocument, aData) {
if ("xpath" in aData) {
for each (let [xpath, value] in Iterator(aData.xpath)) {
let node = XPathGenerator.resolve(aDocument, xpath);
if (node) {
this.restoreFormValue(node, value, aDocument);
}
}
}
if ("id" in aData) {
for each (let [id, value] in Iterator(aData.id)) {
let node = aDocument.getElementById(id);
if (node) {
this.restoreFormValue(node, value, aDocument);
}
}
}
},
/**
* Low-level function to restore a form value to a DOMNode.
*
* If you want a higher-level interface, see mergeFormData().
*
* When the value is changed, the function will fire the appropriate DOM
* events.
*
* @param aNode
* DOMNode to set form value on.
* @param aValue
* Value to set form element to.
* @param aDocument [optional]
* DOMDocument node belongs to. If not defined, node.ownerDocument
* is used.
*/
restoreFormValue: function(aNode, aValue, aDocument) {
aDocument = aDocument || aNode.ownerDocument;
let eventType;
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;
eventType = "input";
} else if (typeof aValue == "boolean") {
// Don't dispatch a change event for no change.
if (aNode.checked == aValue) {
return;
}
aNode.checked = aValue;
eventType = "change";
} else if (typeof aValue == "number") {
// handle select backwards compatibility, example { "#id" : index }
// We saved the value blindly since selects take more work to determine
// default values. So now we should check to avoid unnecessary events.
if (aNode.selectedIndex == aValue) {
return;
}
if (aValue < aNode.options.length) {
aNode.selectedIndex = aValue;
eventType = "change";
}
} else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
// handle select new format
// 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;
break;
}
}
eventType = "change";
} else if (aValue && aValue.fileList && aValue.type == "file" &&
aNode.type == "file") {
aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
eventType = "input";
} else if (aValue && typeof aValue.indexOf == "function" && 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) {
eventType = "change";
}
});
}
// Fire events for this node if applicable
if (eventType) {
let event = aDocument.createEvent("UIEvents");
event.initUIEvent(eventType, true, true, aDocument.defaultView, 0);
aNode.dispatchEvent(event);
}
}
};