Convert feed processor data away from XPCOM

This changes the feed processor XPCOM array and property bags to native arrays
and objects.
This commit is contained in:
J. Ryan Stinnett 2021-05-09 00:20:07 +01:00
parent 75eb8c51d4
commit be80598c6b
2 changed files with 110 additions and 190 deletions

View file

@ -108,7 +108,7 @@ Zotero.FeedReader = function (url) {
let items = this._feed.items;
if (items && items.length) {
for (let i = 0; i < items.length; i++) {
let item = items.queryElementAt(i, Components.interfaces.nsIFeedEntry);
let item = items[i];
if (!item) continue;
let feedItem = Zotero.FeedReader._getFeedItem(item, this._feedProperties);
@ -295,7 +295,7 @@ Zotero.FeedReader._processCreators = function (feedEntry, field, role) {
try {
let personArr = feedEntry[field]; // Seems like this part can throw if there is no author data in the feed
for (let i = 0; i < personArr.length; i++) {
let person = personArr.queryElementAt(i, Components.interfaces.nsIFeedPerson);
let person = personArr[i];
if (!person || !person.name) continue;
let name = Zotero.Utilities.cleanTags(Zotero.Utilities.trimInternal(person.name));
@ -321,8 +321,6 @@ Zotero.FeedReader._processCreators = function (feedEntry, field, role) {
}
}
catch (e) {
if (e.result != Components.results.NS_ERROR_FAILURE) throw e;
if (field != 'authors') return [];
// ieeexplore places these in "authors"... sigh
@ -502,18 +500,16 @@ let ns = {
};
Zotero.FeedReader._getFeedField = function (feedEntry, field, namespace) {
let prefix = namespace ? ns[namespace] || 'null' : '';
try {
return feedEntry.fields.getPropertyAsAUTF8String(prefix + field);
if (feedEntry.fields[prefix + field]) {
return feedEntry.fields[prefix + field];
}
catch (e) {}
try {
if (namespace && !ns[namespace]) {
prefix = namespace + ':';
return feedEntry.fields.getPropertyAsAUTF8String(prefix + field);
if (namespace && !ns[namespace]) {
prefix = namespace + ':';
if (feedEntry.fields[prefix + field]) {
return feedEntry.fields[prefix + field];
}
}
catch (e) { }
return null;
};
@ -523,9 +519,9 @@ Zotero.FeedReader._getEnclosedItems = function (feedEntry) {
if (feedEntry.enclosures) {
for (let i = 0; i < feedEntry.enclosures.length; i++) {
let elem = feedEntry.enclosures.queryElementAt(0, Components.interfaces.nsIPropertyBag2);
if (elem.get('url')) {
let enclosedItem = { url: elem.get('url'), contentType: elem.get('type') || '' };
let elem = feedEntry.enclosures[0];
if (elem.url) {
let enclosedItem = { url: elem.url, contentType: elem.type || '' };
enclosedItems.push(enclosedItem);
}
}

View file

@ -10,8 +10,6 @@ function LOG(str) {
Zotero.debug("Feed Processor: " + str);
}
const BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1";
const ARRAY_CONTRACTID = "@mozilla.org/array;1";
const SAX_CONTRACTID = "@mozilla.org/saxparser/xmlreader;1";
const PARSERUTILS_CONTRACTID = "@mozilla.org/parserutils;1";
@ -74,12 +72,12 @@ const IANA_URI = "http://www.iana.org/assignments/relation/";
function findAtomLinks(rel, links) {
var rvLinks = [];
for (var i = 0; i < links.length; ++i) {
var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2);
var linkElement = links[i];
// atom:link MUST have @href
if (bagHasKey(linkElement, "href")) {
if (linkElement.href) {
var relAttribute = null;
if (bagHasKey(linkElement, "rel")) {
relAttribute = linkElement.getPropertyAsAString("rel");
if (linkElement.rel) {
relAttribute = linkElement.rel;
}
if ((!relAttribute && rel == "alternate") || relAttribute == rel) {
rvLinks.push(linkElement);
@ -103,34 +101,9 @@ function xmlEscape(s) {
return s;
}
function arrayContains(array, element) {
for (var i = 0; i < array.length; ++i) {
if (array[i] == element) {
return true;
}
}
return false;
}
// XXX add hasKey to nsIPropertyBag
function bagHasKey(bag, key) {
try {
bag.getProperty(key);
return true;
}
catch (e) {
return false;
}
}
function makePropGetter(key) {
return function (bag) {
try {
return bag.getProperty(key);
}
catch (e) {
}
return null;
return function(bag) {
return bag[key];
};
}
@ -179,12 +152,12 @@ FeedResult.prototype = {
function Feed() {
this.subtitle = null;
this.title = null;
this.items = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
this.items = [];
this.link = null;
this.id = null;
this.generator = null;
this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
this.authors = [];
this.contributors = [];
this.baseURI = null;
this.enclosureCount = 0;
this.type = Ci.nsIFeed.TYPE_FEED;
@ -226,10 +199,10 @@ Feed.prototype = {
normalize: function () {
fieldsToObj(this, this.searchLists);
if (this.skipDays) {
this.skipDays = this.skipDays.getProperty("days");
this.skipDays = this.skipDays.days;
}
if (this.skipHours) {
this.skipHours = this.skipHours.getProperty("hours");
this.skipHours = this.skipHours.hours;
}
if (this.updated) {
@ -237,14 +210,14 @@ Feed.prototype = {
}
// Assign Atom link if needed
if (bagHasKey(this.fields, "links")) {
if (this.fields.links) {
this._atomLinksToURI();
}
this._calcEnclosureCountAndFeedType();
// Resolve relative image links
if (this.image && bagHasKey(this.image, "url")) {
if (this.image && this.image.url) {
this._resolveImageLink();
}
@ -259,16 +232,15 @@ Feed.prototype = {
var otherCount = 0;
for (var i = 0; i < this.items.length; ++i) {
var entry = this.items.queryElementAt(i, Ci.nsIFeedEntry);
entry.QueryInterface(Ci.nsIFeedContainer);
var entry = this.items[i];
if (entry.enclosures && entry.enclosures.length > 0) {
++entriesWithEnclosures;
for (var e = 0; e < entry.enclosures.length; ++e) {
var enc = entry.enclosures.queryElementAt(e, Ci.nsIWritablePropertyBag2);
if (enc.hasKey("type")) {
var enctype = enc.get("type");
var enc = entry.enclosures[e];
if (enc.type) {
var enctype = enc.type;
if (/^audio/.test(enctype)) {
++audioCount;
@ -317,13 +289,13 @@ Feed.prototype = {
},
_atomLinksToURI: function () {
var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
var links = this.fields.links;
var alternates = findAtomLinks("alternate", links);
if (alternates.length > 0) {
var href = alternates[0].getPropertyAsAString("href");
var href = alternates[0].href;
var base;
if (bagHasKey(alternates[0], "xml:base")) {
base = alternates[0].getPropertyAsAString("xml:base");
if (alternates[0]["xml:base"]) {
base = alternates[0]["xml:base"];
}
this.link = this._resolveURI(href, base);
}
@ -331,12 +303,12 @@ Feed.prototype = {
_resolveImageLink: function () {
var base;
if (bagHasKey(this.image, "xml:base")) {
base = this.image.getPropertyAsAString("xml:base");
if (this.image["xml:base"]) {
base = this.image["xml:base"];
}
var url = this._resolveURI(this.image.getPropertyAsAString("url"), base);
var url = this._resolveURI(this.image.url, base);
if (url) {
this.image.setPropertyAsAString("url", url.spec);
this.image.url = url.spec;
}
},
@ -357,9 +329,9 @@ Feed.prototype = {
_resetBagMembersToRawText: function (fieldLists) {
for (var i = 0; i < fieldLists.length; i++) {
for (var j = 0; j < fieldLists[i].length; j++) {
if (bagHasKey(this.fields, fieldLists[i][j])) {
var textConstruct = this.fields.getProperty(fieldLists[i][j]);
this.fields.setPropertyAsAString(fieldLists[i][j], textConstruct.text);
if (this.fields[fieldLists[i][j]]) {
var textConstruct = this.fields[fieldLists[i][j]];
this.fields[fieldLists[i][j]] = textConstruct.text;
}
}
}
@ -371,14 +343,14 @@ function Entry() {
this.summary = null;
this.content = null;
this.title = null;
this.fields = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2);
this.fields = {};
this.link = null;
this.id = null;
this.baseURI = null;
this.updated = null;
this.published = null;
this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
this.authors = [];
this.contributors = [];
}
Entry.prototype = {
@ -420,7 +392,7 @@ Entry.prototype = {
fieldsToObj(this, this.searchLists);
// Assign Atom link if needed
if (bagHasKey(this.fields, "links")) {
if (this.fields.links) {
this._atomLinksToURI();
}
@ -428,16 +400,16 @@ Entry.prototype = {
this._populateEnclosures();
// The link might be a guid w/ permalink=true
if (!this.link && bagHasKey(this.fields, "guid")) {
var guid = this.fields.getProperty("guid");
if (!this.link && this.fields.guid) {
var guid = this.fields.guid;
var isPermaLink = true;
if (bagHasKey(guid, "isPermaLink")) {
isPermaLink = guid.getProperty("isPermaLink").toLowerCase() != "false";
if (guid.isPermaLink) {
isPermaLink = guid.isPermaLink.toLowerCase() != "false";
}
if (guid && isPermaLink) {
this.link = strToURI(guid.getProperty("guid"));
this.link = strToURI(guid.guid);
}
}
@ -456,27 +428,27 @@ Entry.prototype = {
},
_populateEnclosures: function () {
if (bagHasKey(this.fields, "links")) {
if (this.fields.links) {
this._atomLinksToEnclosures();
}
// Add RSS2 enclosure to enclosures
if (bagHasKey(this.fields, "enclosure")) {
if (this.fields.enclosure) {
this._enclosureToEnclosures();
}
// Add media:content to enclosures
if (bagHasKey(this.fields, "mediacontent")) {
if (this.fields.mediacontent) {
this._mediaToEnclosures("mediacontent");
}
// Add media:thumbnail to enclosures
if (bagHasKey(this.fields, "mediathumbnail")) {
if (this.fields.mediathumbnail) {
this._mediaToEnclosures("mediathumbnail");
}
// Add media:content in media:group to enclosures
if (bagHasKey(this.fields, "mediagroup")) {
if (this.fields.mediagroup) {
this._mediaToEnclosures("mediagroup", "mediacontent");
}
},
@ -486,7 +458,7 @@ Entry.prototype = {
_addToEnclosures: function (newEnc) {
// items we add to the enclosures array get displayed in the FeedWriter and
// they must have non-empty urls.
if (!bagHasKey(newEnc, "url") || newEnc.getPropertyAsAString("url") == "") {
if (!newEnc.url || newEnc.url == "") {
return;
}
@ -494,40 +466,37 @@ Entry.prototype = {
this.__enclosureMap = {};
}
var previousEnc = this.__enclosureMap[newEnc.getPropertyAsAString("url")];
var previousEnc = this.__enclosureMap[newEnc.url];
if (previousEnc != undefined) {
previousEnc.QueryInterface(Ci.nsIWritablePropertyBag2);
if (!bagHasKey(previousEnc, "type") && bagHasKey(newEnc, "type")) {
previousEnc.setPropertyAsAString("type", newEnc.getPropertyAsAString("type"));
if (!previousEnc.type && newEnc.type) {
previousEnc.type = newEnc.type;
try {
let handlerInfoWrapper = gMimeService.getFromTypeAndExtension(newEnc.getPropertyAsAString("type"), null);
let handlerInfoWrapper = gMimeService.getFromTypeAndExtension(newEnc.type, null);
if (handlerInfoWrapper && handlerInfoWrapper.description) {
previousEnc.setPropertyAsAString("typeDesc", handlerInfoWrapper.description);
previousEnc.typeDesc = handlerInfoWrapper.description;
}
}
catch (ext) {}
}
if (!bagHasKey(previousEnc, "length") && bagHasKey(newEnc, "length")) {
previousEnc.setPropertyAsAString("length", newEnc.getPropertyAsAString("length"));
if (!previousEnc.length && newEnc.length) {
previousEnc.length = newEnc.length;
}
return;
}
if (this.enclosures === null) {
this.enclosures = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
this.enclosures.QueryInterface(Ci.nsIMutableArray);
this.enclosures = [];
}
this.enclosures.appendElement(newEnc);
this.__enclosureMap[newEnc.getPropertyAsAString("url")] = newEnc;
this.enclosures.push(newEnc);
this.__enclosureMap[newEnc.url] = newEnc;
},
_atomLinksToEnclosures: function () {
var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
var links = this.fields.links;
var encLinks = findAtomLinks("enclosure", links);
if (encLinks.length == 0) {
return;
@ -537,19 +506,19 @@ Entry.prototype = {
var link = encLinks[i];
// an enclosure must have an href
if (!(link.getProperty("href"))) {
if (!link.href) {
return;
}
var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
var enc = {};
// copy Atom bits over to equivalent enclosure bits
enc.setPropertyAsAString("url", link.getPropertyAsAString("href"));
if (bagHasKey(link, "type")) {
enc.setPropertyAsAString("type", link.getPropertyAsAString("type"));
enc.url = link.href;
if (link.type) {
enc.type = link.type;
}
if (bagHasKey(link, "length")) {
enc.setPropertyAsAString("length", link.getPropertyAsAString("length"));
if (link.length) {
enc.length = link.length;
}
this._addToEnclosures(enc);
@ -557,9 +526,9 @@ Entry.prototype = {
},
_enclosureToEnclosures: function () {
var enc = this.fields.getPropertyAsInterface("enclosure", Ci.nsIPropertyBag2);
var enc = this.fields.enclosure;
if (!(enc.getProperty("url"))) {
if (!enc.url) {
return;
}
@ -572,37 +541,37 @@ Entry.prototype = {
// If a contentType is specified, the mediaType is a simple propertybag,
// and the contentType is an array inside it.
if (contentType) {
var group = this.fields.getPropertyAsInterface(mediaType, Ci.nsIPropertyBag2);
content = group.getPropertyAsInterface(contentType, Ci.nsIArray);
var group = this.fields[mediaType];
content = group[contentType];
}
else {
content = this.fields.getPropertyAsInterface(mediaType, Ci.nsIArray);
content = this.fields[mediaType];
}
for (var i = 0; i < content.length; ++i) {
var contentElement = content.queryElementAt(i, Ci.nsIWritablePropertyBag2);
var contentElement = content[i];
// media:content don't require url, but if it's not there, we should
// skip it.
if (!bagHasKey(contentElement, "url")) {
if (!contentElement.url) {
continue;
}
var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
var enc = {};
// copy media:content bits over to equivalent enclosure bits
enc.setPropertyAsAString("url", contentElement.getPropertyAsAString("url"));
if (bagHasKey(contentElement, "type")) {
enc.setPropertyAsAString("type", contentElement.getPropertyAsAString("type"));
enc.url = contentElement.url;
if (contentElement.type) {
enc.type = contentElement.type;
}
else if (mediaType == "mediathumbnail") {
// thumbnails won't have a type, but default to image types
enc.setPropertyAsAString("type", "image/*");
enc.setPropertyAsBool("thumbnail", true);
enc.type = "image/*";
enc.thumbnail = true;
}
if (bagHasKey(contentElement, "fileSize")) {
enc.setPropertyAsAString("length", contentElement.getPropertyAsAString("fileSize"));
if (contentElement.fileSize) {
enc.length = contentElement.fileSize;
}
this._addToEnclosures(enc);
@ -721,11 +690,7 @@ function fieldsToObj(container, fields) {
props = searchList[i];
prop = null;
field = isArray(props) ? props[0] : props;
try {
prop = container.fields.getProperty(field);
}
catch (e) {
}
prop = container.fields[field];
if (prop) {
prop = isArray(props) ? props[1](prop) : prop;
container[key] = prop;
@ -734,39 +699,27 @@ function fieldsToObj(container, fields) {
}
}
/**
* Lower cases an element's localName property
* @param element A DOM element.
*
* @returns The lower case localName property of the specified element
*/
function LC(element) {
return element.localName.toLowerCase();
}
// TODO move these post-processor functions
// create a generator element
function atomGenerator(s, generator) {
generator.QueryInterface(Ci.nsIFeedGenerator);
generator.agent = s.trim();
return generator;
}
// post-process atom:logo to create an RSS2-like structure
function atomLogo(s, logo) {
logo.setPropertyAsAString("url", s.trim());
logo.url = s.trim();
}
// post-process an RSS category, map it to the Atom fields.
function rssCatTerm(s, cat) {
// add slash handling?
cat.setPropertyAsAString("term", s.trim());
cat.term = s.trim();
return cat;
}
// post-process a GUID
function rssGuid(s, guid) {
guid.setPropertyAsAString("guid", s.trim());
guid.guid = s.trim();
return guid;
}
@ -784,7 +737,6 @@ function rssGuid(s, guid) {
// fields.
//
function rssAuthor(s, author) {
author.QueryInterface(Ci.nsIFeedPerson);
// check for RSS2 string format
var chars = s.trim();
var matches = chars.match(/(.*)\((.*)\)/);
@ -818,17 +770,6 @@ function rssAuthor(s, author) {
return author;
}
//
// skipHours and skipDays map to arrays, so we need to change the
// string to an nsISupports in order to stick it in there.
//
function rssArrayElement(s) {
var str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
str.data = s;
str.QueryInterface(Ci.nsISupportsString);
return str;
}
/**
* Tries parsing a string through the JavaScript Date object.
* @param aDateString
@ -1045,7 +986,7 @@ function WrapperElementInfo(fieldName) {
function FeedProcessor() {
this._reader = Cc[SAX_CONTRACTID].createInstance(Ci.nsISAXXMLReader);
this._buf = "";
this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
this._feed = {};
this._handlerStack = [];
this._xmlBaseStack = []; // sparse array keyed to nesting depth
this._depth = 0;
@ -1129,11 +1070,11 @@ function FeedProcessor() {
},
"IN_SKIPDAYS": {
"day": new ElementInfo("days", null, rssArrayElement, true),
"day": new ElementInfo("days", null, null, true),
},
"IN_SKIPHOURS": {
"hour": new ElementInfo("hours", null, rssArrayElement, true),
"hour": new ElementInfo("hours", null, null, true),
},
"IN_MEDIAGROUP": {
@ -1476,7 +1417,7 @@ FeedProcessor.prototype = {
obj.attributes = attributes; // just set the SAX attributes
}
else {
obj = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
obj = {};
this._mapAttributes(obj, attributes);
}
@ -1489,27 +1430,15 @@ FeedProcessor.prototype = {
var container = this._stack[this._stack.length - 1][0];
// Check to see if it has the property
var prop;
try {
prop = container.getProperty(elementInfo.fieldName);
}
catch (e) {
}
var prop = container[elementInfo.fieldName];
if (elementInfo.isArray) {
if (!prop) {
container.setPropertyAsInterface(
elementInfo.fieldName,
Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray),
);
container[elementInfo.fieldName] = [];
}
newProp = container.getProperty(elementInfo.fieldName);
// XXX This QI should not be necessary, but XPConnect seems to fly
// off the handle in the browser, and loses track of the interface
// on large files. Bug 335638.
newProp.QueryInterface(Ci.nsIMutableArray);
newProp.appendElement(obj);
newProp = container[elementInfo.fieldName];
newProp.push(obj);
// If new object is an nsIFeedContainer, we want to deal with
// its member nsIPropertyBag instead.
@ -1520,9 +1449,9 @@ FeedProcessor.prototype = {
else {
// If it doesn't, set it.
if (!prop) {
container.setPropertyAsInterface(elementInfo.fieldName, obj);
container[elementInfo.fieldName] = obj;
}
newProp = container.getProperty(elementInfo.fieldName);
newProp = container[elementInfo.fieldName];
}
// make our new state name, and push the property onto the stack
@ -1545,7 +1474,7 @@ FeedProcessor.prototype = {
// If it's an array and we have to post-process,
// grab the last element
if (isArray) {
element = container.queryElementAt(container.length - 1, Ci.nsISupports);
element = container[container.length - 1];
}
else {
element = container;
@ -1564,7 +1493,7 @@ FeedProcessor.prototype = {
// If it's an array, re-set the last element
if (isArray) {
container.replaceElementAt(element, container.length - 1);
container[container.length - 1] = element;
}
},
@ -1588,7 +1517,7 @@ FeedProcessor.prototype = {
for (var i = 0; i < attributes.length; ++i) {
var key = this._prefixForNS(attributes.getURI(i)) + attributes.getLocalName(i);
var val = attributes.getValue(i);
bag.setPropertyAsAString(key, val);
bag[key] = val;
}
},
@ -1638,13 +1567,8 @@ FeedProcessor.prototype = {
var contract = this._handlerStack[this._depth].containerClass;
// check if it's something specific, but not an entry
if (contract && contract != Entry) {
var el = container.queryElementAt(container.length - 1,
Ci.nsIFeedElementBase);
// XXX there must be a way to flatten these interfaces
if (contract == Person) {
el.QueryInterface(Ci.nsIFeedPerson);
}
else {
var el = container[container.length - 1];
if (contract != Person) {
return; // don't know about this interface
}
@ -1679,7 +1603,7 @@ FeedProcessor.prototype = {
// the rest of the function deals with entry- and feed-level stuff
return;
}
container = container.queryElementAt(container.length - 1, Ci.nsIWritablePropertyBag2);
container = container[container.length - 1];
}
// Make the buffer our new property
@ -1716,10 +1640,10 @@ FeedProcessor.prototype = {
}
newProp.type = type;
newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
container.setPropertyAsInterface(propName, newProp);
container[propName] = newProp;
}
else {
container.setPropertyAsAString(propName, chars);
container[propName] = chars;
}
},
@ -1743,7 +1667,7 @@ FeedProcessor.prototype = {
newProp.text = chars;
newProp.type = "xhtml";
newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
container.setPropertyAsInterface(this._prefixForNS(uri) + localName, newProp);
container[this._prefixForNS(uri) + localName] = newProp;
// XHTML will cause us to peek too far. The XHTML handler will
// send us an end element to call. RFC4287-valid feeds allow a