mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-06 16:38:55 +00:00
231 lines
7.5 KiB
JavaScript
231 lines
7.5 KiB
JavaScript
/* 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 DocumentUtils_getFormData(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 DocumentUtils_mergeFormData(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 DocumentUtils_restoreFormValue(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);
|
|
}
|
|
}
|
|
};
|