From eaa01901ef2f360e767ff2a9541eae6725658d66 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Sat, 22 May 2021 18:44:42 +0100 Subject: [PATCH] Wire up new feed processor to Zotero's feed reader This connects the new feed processor to Zotero's existing feed reader. The new feed processor assumes a content window environment, so a sandbox in a parent window is used to load it. --- chrome/content/zotero/xpcom/feedReader.js | 71 +++++++++++++++++------ test/tests/feedReaderTest.js | 14 ++++- test/tests/feedTest.js | 8 ++- 3 files changed, 72 insertions(+), 21 deletions(-) diff --git a/chrome/content/zotero/xpcom/feedReader.js b/chrome/content/zotero/xpcom/feedReader.js index 6a9d1abc23..2326d7e385 100644 --- a/chrome/content/zotero/xpcom/feedReader.js +++ b/chrome/content/zotero/xpcom/feedReader.js @@ -130,11 +130,49 @@ Zotero.FeedReader = function (url) { lastItem.resolve(null); }.bind(this)); - // Set up asynchronous feed processor - let feedProcessor = Components.classes["@mozilla.org/feed-processor;1"] - .createInstance(Components.interfaces.nsIFeedProcessor); + // The feed processor and related modules assume a content window environment, so we'll simulate + // one via a sandbox in a parent window. You might think we could jump straight to + // `hiddenDOMWindow` as a parent window, since it does indeed exist on all platforms... + // However, when loading scripts into the `hiddenDOMWindow` on Windows and Linux, they get + // stuck in some lazily parsed state which bizarrely drops function prototypes. To avoid this, + // we prefer other parent windows first, which work fine on all platforms. + let parentWindow = Services.wm.getMostRecentWindow("navigator:browser"); + if (!parentWindow) { + parentWindow = Services.ww.activeWindow; + } + // Use the hidden DOM window on macOS with the main window closed + if (!parentWindow) { + parentWindow = Services.appShell.hiddenDOMWindow; + } + if (!parentWindow) { + this.terminate("Parent window not available for feed reader"); + return; + } - let feedUrl = Services.io.newURI(url, null, null); + const sandbox = new Cu.Sandbox(parentWindow, { + sandboxPrototype: parentWindow, + sandboxName: "Feed Processor", + }); + sandbox.Zotero = { + debug: Components.utils.exportFunction(Zotero.debug, sandbox), + }; + + Services.scriptloader.loadSubScript("resource://zotero/feeds/FeedProcessor.js", sandbox); + Services.scriptloader.loadSubScript("resource://zotero/feeds/SAXXMLReader.js", sandbox); + + // Set up asynchronous feed processor + const { FeedProcessor } = sandbox; + const feedProcessor = new FeedProcessor(); + if (!feedProcessor.parseAsync) { + this.terminate("Feed processor failed to load in parent window"); + return; + } + + // Borrow web utils to fetch feed content + const { fetch, URL } = parentWindow; + + // Pass along the URL + const feedUrl = new URL(url); feedProcessor.parseAsync(null, feedUrl); /* @@ -148,19 +186,21 @@ Zotero.FeedReader = function (url) { this.terminate("No Feed"); return; } - - let newFeed = result.doc.QueryInterface(Components.interfaces.nsIFeed); - feedFetched.resolve(newFeed); + feedFetched.resolve(result.doc); } }; - Zotero.debug("FeedReader: Fetching feed from " + feedUrl.spec); + Zotero.debug("FeedReader: Fetching feed from " + feedUrl); - this._channel = Services.io.newChannelFromURI2(feedUrl, null, - Services.scriptSecurityManager.getSystemPrincipal(), null, - Ci.nsILoadInfo.SEC_NORMAL, Ci.nsIContentPolicy.TYPE_OTHER); - this._channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - this._channel.asyncOpen(feedProcessor, null); // Sends an HTTP request + // Fetch and start processing + fetch(feedUrl, { + cache: "no-store", + }).then((response) => { + return feedProcessor.onResponseAvailable(response); + }).catch((e) => { + Zotero.debug(e); + this.terminate("Processing failed"); + }); }; /* @@ -195,11 +235,6 @@ Zotero.FeedReader.prototype.terminate = function (status) { er.handledRejection = true; lastItem.reject(er); } - - // Close feed connection - if (this._channel.isPending()) { - this._channel.cancel(Components.results.NS_BINDING_ABORTED); - } }; Zotero.defineProperty(Zotero.FeedReader.prototype, 'feedProperties', { diff --git a/test/tests/feedReaderTest.js b/test/tests/feedReaderTest.js index 7b34a3efc2..402c0675a7 100644 --- a/test/tests/feedReaderTest.js +++ b/test/tests/feedReaderTest.js @@ -35,8 +35,18 @@ describe("Zotero.FeedReader", function () { var atomFeedURL = getTestDataUrl("feed.atom"); var mediaFeedURL = getTestDataUrl("feedMedia.xml"); - after(function* () { - yield clearFeeds(); + var win; + + before(async function() { + // Browser window is needed as parent window to load the feed reader scripts. + win = await loadBrowserWindow(); + }); + + after(async function() { + if (win) { + win.close(); + } + await clearFeeds(); }); describe('FeedReader()', function () { diff --git a/test/tests/feedTest.js b/test/tests/feedTest.js index 15e7937983..6766f47ea0 100644 --- a/test/tests/feedTest.js +++ b/test/tests/feedTest.js @@ -311,8 +311,11 @@ describe("Zotero.Feed", function() { var feed, scheduleNextFeedCheck; var feedUrl = getTestDataUrl("feed.rss"); var modifiedFeedUrl = getTestDataUrl("feedModified.rss"); + var win; - before(function() { + before(async function() { + // Browser window is needed as parent window to load the feed reader scripts. + win = await loadBrowserWindow(); scheduleNextFeedCheck = sinon.stub(Zotero.Feeds, 'scheduleNextFeedCheck').resolves(); }); @@ -328,6 +331,9 @@ describe("Zotero.Feed", function() { }); after(function() { + if (win) { + win.close(); + } scheduleNextFeedCheck.restore(); });