Bug 1341191 - Fix messaging priv escalation of feed reader, simplify messaging from parent to child. Reduce race conditions on page. r=Gijs

This commit is contained in:
Jonathan Kingston 2017-03-19 06:29:17 +00:00
Родитель 863a41136f
Коммит 49113725c4
4 изменённых файлов: 480 добавлений и 339 удалений

Просмотреть файл

@ -3,11 +3,136 @@
* 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/. */
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
const PREF_SELECTED_APP = "browser.feeds.handlers.application";
const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
const PREF_SELECTED_ACTION = "browser.feeds.handler";
const PREF_SELECTED_READER = "browser.feeds.handler.default";
const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
const PREF_UPDATE_DELAY = 2000;
const SETTABLE_PREFS = new Set([
PREF_VIDEO_SELECTED_ACTION,
PREF_AUDIO_SELECTED_ACTION,
PREF_SELECTED_ACTION,
PREF_VIDEO_SELECTED_READER,
PREF_AUDIO_SELECTED_READER,
PREF_SELECTED_READER,
PREF_VIDEO_SELECTED_WEB,
PREF_AUDIO_SELECTED_WEB,
PREF_SELECTED_WEB
]);
const EXECUTABLE_PREFS = new Set([
PREF_SELECTED_APP,
PREF_VIDEO_SELECTED_APP,
PREF_AUDIO_SELECTED_APP
]);
const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]);
const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]);
XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG",
"feeds.log", false);
function LOG(str) {
if (SHOULD_LOG)
dump("*** Feeds: " + str + "\n");
}
function getPrefActionForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_ACTION;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_ACTION;
default:
return PREF_SELECTED_ACTION;
}
}
function getPrefReaderForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_READER;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_READER;
default:
return PREF_SELECTED_READER;
}
}
function getPrefWebForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_WEB;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_WEB;
default:
return PREF_SELECTED_WEB;
}
}
function getPrefAppForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_APP;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_APP;
default:
return PREF_SELECTED_APP;
}
}
/**
* Maps a feed type to a maybe-feed mimetype.
*/
function getMimeTypeForFeedType(aFeedType) {
switch (aFeedType) {
case Ci.nsIFeed.TYPE_VIDEO:
return TYPE_MAYBE_VIDEO_FEED;
case Ci.nsIFeed.TYPE_AUDIO:
return TYPE_MAYBE_AUDIO_FEED;
default:
return TYPE_MAYBE_FEED;
}
}
/**
* The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
* and shows UI when they are discovered.
*/
var FeedHandler = {
_prefChangeCallback: null,
/** Called when the user clicks on the Subscribe to This Page... menu item,
* or when the user clicks the feed button when the page contains multiple
* feeds.
@ -195,7 +320,8 @@ var FeedHandler = {
return file.leafName;
},
chooseClientApp(aTitle, aPrefName, aBrowser) {
_chooseClientApp(aTitle, aTypeName, aBrowser) {
const prefName = getPrefAppForType(aTypeName);
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen);
@ -222,7 +348,7 @@ var FeedHandler = {
}
if (fp.file.leafName != appName) {
Services.prefs.setComplexValue(aPrefName, Ci.nsILocalFile, selectedApp);
Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp);
aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
{ name: this._getFileDisplayName(selectedApp),
type: "SelectedAppMenuItem" });
@ -277,70 +403,243 @@ var FeedHandler = {
}
},
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
init() {
window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this);
window.messageManager.addMessageListener("FeedWriter:RequestClientAppName", this);
window.messageManager.addMessageListener("FeedWriter:SetFeedCharPref", this);
window.messageManager.addMessageListener("FeedWriter:SetFeedComplexString", this);
window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this);
window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this);
window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this);
const prefs = Services.prefs;
prefs.addObserver(PREF_SELECTED_ACTION, this, true);
prefs.addObserver(PREF_SELECTED_READER, this, true);
prefs.addObserver(PREF_SELECTED_WEB, this, true);
prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true);
prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true);
prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true);
prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true);
prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true);
prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true);
},
uninit() {
Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this);
this._prefChangeCallback = null;
},
// nsIObserver
observe(subject, topic, data) {
if (topic == "nsPref:changed") {
LOG(`Pref changed ${data}`)
if (this._prefChangeCallback) {
this._prefChangeCallback.disarm();
}
// Multiple prefs are set at the same time, debounce to reduce noise
// This can happen in one feed and we want to message all feed pages
this._prefChangeCallback = new DeferredTask(() => {
this._prefChanged(data);
}, PREF_UPDATE_DELAY);
this._prefChangeCallback.arm();
}
},
_prefChanged(prefName) {
// Don't observe for PREF_*SELECTED_APP as user likely just picked one
// That is also handled by SetApplicationLauncherMenuItem call
// Rather than the others which happen on subscription
switch (prefName) {
case PREF_SELECTED_READER:
case PREF_SELECTED_WEB:
case PREF_VIDEO_SELECTED_READER:
case PREF_VIDEO_SELECTED_WEB:
case PREF_AUDIO_SELECTED_READER:
case PREF_AUDIO_SELECTED_WEB:
case PREF_SELECTED_ACTION:
case PREF_VIDEO_SELECTED_ACTION:
case PREF_AUDIO_SELECTED_ACTION:
const response = {
default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED),
[Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO),
[Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO)
};
Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated",
response);
break;
}
},
_initSubscriptionUIResponse(feedType) {
const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType));
const handlers = [];
for (let handler of handlersRaw) {
LOG(`Handler found: ${handler}`);
handlers.push({
name: handler.name,
uri: handler.uri
});
}
let showFirstRunUI = true;
// eslint-disable-next-line mozilla/use-default-preference-values
try {
showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
} catch (ex) { }
const response = { handlers, showFirstRunUI };
let selectedClientApp;
const feedTypePref = getPrefAppForType(feedType);
try {
selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile);
} catch (ex) {
// Just do nothing, then we won't bother populating
}
let defaultClientApp = null;
try {
// This can sometimes not exist
defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
.getService(Ci.nsIShellService)
.defaultFeedReader;
} catch (ex) {
// Just do nothing, then we don't bother populating
}
if (selectedClientApp && selectedClientApp.exists()) {
if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
// Only set the default menu item if it differs from the selected one
response.defaultMenuItem = this._getFileDisplayName(defaultClientApp);
}
response.selectedMenuItem = this._getFileDisplayName(selectedClientApp);
}
response.reader = this._getReaderForType(feedType);
return response;
},
_setPref(aPrefName, aPrefValue, aIsComplex = false) {
LOG(`FeedWriter._setPref ${aPrefName}`);
// Ensure we have a pref that is settable
if (aPrefName && SETTABLE_PREFS.has(aPrefName)) {
if (aIsComplex) {
const supportsString = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
supportsString.data = aPrefValue;
Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, supportsString);
} else {
Services.prefs.setCharPref(aPrefName, aPrefValue);
}
} else {
LOG(`FeedWriter._setPref ${aPrefName} not allowed`);
}
},
_getReaderForType(feedType) {
let prefs = Services.prefs;
let handler = "bookmarks";
let url;
// eslint-disable-next-line mozilla/use-default-preference-values
try {
handler = prefs.getCharPref(getPrefReaderForType(feedType));
} catch (ex) { }
if (handler === "web") {
try {
url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
} catch (ex) {
LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
url = null;
}
}
const alwaysUse = this._getAlwaysUseState(feedType);
const action = prefs.getCharPref(getPrefActionForType(feedType));
return { handler, url, alwaysUse, action };
},
_getAlwaysUseState(feedType) {
try {
return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask";
} catch (ex) { }
return false;
},
receiveMessage(msg) {
let handler;
switch (msg.name) {
case "FeedWriter:ChooseClientApp":
this.chooseClientApp(msg.data.title, msg.data.prefName, msg.target);
case "FeedWriter:GetSubscriptionUI":
const response = this._initSubscriptionUIResponse(msg.data.feedType);
msg.target.messageManager
.sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse",
response);
break;
case "FeedWriter:RequestClientAppName":
let selectedClientApp;
try {
selectedClientApp = Services.prefs.getComplexValue(msg.data.feedTypePref, Ci.nsILocalFile);
} catch (ex) {
// Just do nothing, then we won't bother populating
}
let defaultClientApp = null;
try {
// This can sometimes not exist
defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
.getService(Ci.nsIShellService)
.defaultFeedReader;
} catch (ex) {
// Just do nothing, then we don't bother populating
}
if (selectedClientApp && selectedClientApp.exists()) {
if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
// Only set the default menu item if it differs from the selected one
msg.target.messageManager
.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
{ name: this._getFileDisplayName(defaultClientApp),
type: "DefaultAppMenuItem" });
}
msg.target.messageManager
.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
{ name: this._getFileDisplayName(selectedClientApp),
type: "SelectedAppMenuItem" });
}
case "FeedWriter:ChooseClientApp":
this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target);
break;
case "FeedWriter:ShownFirstRun":
Services.prefs.setBoolPref("browser.feeds.showFirstRunUI", false);
Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
break;
case "FeedWriter:SetFeedCharPref":
Services.prefs.setCharPref(msg.data.pref, msg.data.value);
break;
case "FeedWriter:SetFeedComplexString": {
Services.prefs.setStringPref(msg.data.pref, msg.data.value);
break;
}
case "FeedWriter:SetFeedPrefsAndSubscribe":
const settings = msg.data;
if (!settings.action || !VALID_ACTIONS.has(settings.action)) {
LOG(`Invalid action ${settings.action}`);
return;
}
if (!settings.reader || !VALID_READERS.has(settings.reader)) {
LOG(`Invalid reader ${settings.reader}`);
return;
}
const actionPref = getPrefActionForType(settings.feedType);
this._setPref(actionPref, settings.action);
const readerPref = getPrefReaderForType(settings.feedType);
this._setPref(readerPref, settings.reader);
handler = null;
switch (settings.reader) {
case "web":
// This is a web set URI by content using window.registerContentHandler()
// Lets make sure we know about it before setting it
const webPref = getPrefWebForType(settings.feedType);
let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
// If the user provided an invalid web URL this function won't give us a reference
handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri);
if (handler) {
this._setPref(webPref, settings.uri, true);
if (settings.useAsDefault) {
wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler);
}
msg.target.messageManager
.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse",
{ redirect: handler.getHandlerURI(settings.feedLocation) });
} else {
LOG(`No handler found for web ${settings.feedType} ${settings.uri}`);
}
break;
default:
const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
getService(Ci.nsIFeedResultService);
feedService.addToClientReader(settings.feedLocation,
settings.feedTitle,
settings.feedSubtitle,
settings.feedType,
settings.reader);
}
break;
case "FeedConverter:ExecuteClientApp":
this.executeClientApp(msg.data.spec, msg.data.title,
msg.data.subtitle, msg.data.feedHandler);
// Always check feedHandler is from a set array of executable prefs
if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) {
this.executeClientApp(msg.data.spec, msg.data.title,
msg.data.subtitle, msg.data.feedHandler);
} else {
LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`);
}
break;
}
},

Просмотреть файл

@ -223,8 +223,7 @@ FeedConverter.prototype = {
try {
let title = feed.title ? feed.title.plainText() : "";
let desc = feed.subtitle ? feed.subtitle.plainText() : "";
let feedReader = safeGetCharPref(getPrefActionForType(feed.type), "bookmarks");
feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader);
feedService.addToClientReader(result.uri.spec, title, desc, feed.type, handler);
return;
} catch (ex) { /* fallback to preview mode */ }
}

Просмотреть файл

@ -44,83 +44,11 @@ function makeURI(aURLSpec, aCharset) {
const XML_NS = "http://www.w3.org/XML/1998/namespace";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
const PREF_SELECTED_APP = "browser.feeds.handlers.application";
const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
const PREF_SELECTED_ACTION = "browser.feeds.handler";
const PREF_SELECTED_READER = "browser.feeds.handler.default";
const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
const TITLE_ID = "feedTitleText";
const SUBTITLE_ID = "feedSubtitleText";
function getPrefAppForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_APP;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_APP;
default:
return PREF_SELECTED_APP;
}
}
function getPrefWebForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_WEB;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_WEB;
default:
return PREF_SELECTED_WEB;
}
}
function getPrefActionForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_ACTION;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_ACTION;
default:
return PREF_SELECTED_ACTION;
}
}
function getPrefReaderForType(t) {
switch (t) {
case Ci.nsIFeed.TYPE_VIDEO:
return PREF_VIDEO_SELECTED_READER;
case Ci.nsIFeed.TYPE_AUDIO:
return PREF_AUDIO_SELECTED_READER;
default:
return PREF_SELECTED_READER;
}
}
/**
* Converts a number of bytes to the appropriate unit that results in a
* number that needs fewer than 4 digits
@ -153,9 +81,6 @@ function FeedWriter() {
}
FeedWriter.prototype = {
_mimeSvc : Cc["@mozilla.org/mime;1"].
getService(Ci.nsIMIMEService),
_getPropertyAsBag(container, property) {
return container.fields.getProperty(property).
QueryInterface(Ci.nsIPropertyBag2);
@ -226,18 +151,21 @@ FeedWriter.prototype = {
return this._bundle.GetStringFromName(key);
},
_setCheckboxCheckedState(aCheckbox, aValue) {
// see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
let change = (aValue != (aCheckbox.getAttribute("checked") == "true"));
if (aValue)
aCheckbox.setAttribute("checked", "true");
else
aCheckbox.removeAttribute("checked");
_setCheckboxCheckedState(aValue) {
let checkbox = this._document.getElementById("alwaysUse");
if (checkbox) {
// see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
let change = (aValue != (checkbox.getAttribute("checked") == "true"));
if (aValue)
checkbox.setAttribute("checked", "true");
else
checkbox.removeAttribute("checked");
if (change) {
let event = this._document.createEvent("Events");
event.initEvent("CheckboxStateChange", true, true);
aCheckbox.dispatchEvent(event);
if (change) {
let event = this._document.createEvent("Events");
event.initEvent("CheckboxStateChange", true, true);
checkbox.dispatchEvent(event);
}
}
},
@ -290,22 +218,6 @@ FeedWriter.prototype = {
return Ci.nsIFeed.TYPE_FEED;
},
/**
* Maps a feed type to a maybe-feed mimetype.
*/
_getMimeTypeForFeedType() {
switch (this._getFeedType()) {
case Ci.nsIFeed.TYPE_VIDEO:
return TYPE_MAYBE_VIDEO_FEED;
case Ci.nsIFeed.TYPE_AUDIO:
return TYPE_MAYBE_AUDIO_FEED;
default:
return TYPE_MAYBE_FEED;
}
},
/**
* Writes the feed title into the preview document.
* @param container
@ -491,17 +403,11 @@ FeedWriter.prototype = {
if (enc.hasKey("type")) {
type_text = enc.get("type");
try {
let handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
if (handlerInfoWrapper)
type_text = handlerInfoWrapper.description;
if (type_text && type_text.length > 0)
mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
} catch (ex) { }
if (enc.hasKey("typeDesc"))
type_text = enc.get("typeDesc");
if (type_text && type_text.length > 0)
mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
}
if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
@ -596,19 +502,7 @@ FeedWriter.prototype = {
this._subscribeCallback = aCallback;
this._mm.sendAsyncMessage("FeedWriter:ChooseClientApp",
{ title: this._getString("chooseApplicationDialogTitle"),
prefName: getPrefAppForType(this._getFeedType()) });
},
_setAlwaysUseCheckedState(feedType) {
let checkbox = this._document.getElementById("alwaysUse");
if (checkbox) {
let alwaysUse = false;
try {
if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
alwaysUse = true;
} catch (ex) { }
this._setCheckboxCheckedState(checkbox, alwaysUse);
}
feedType: this._getFeedType() });
},
_setSubscribeUsingLabel() {
@ -671,13 +565,13 @@ FeedWriter.prototype = {
}
break;
case "change":
LOG("Change fired");
if (event.target.selectedOptions[0].id == "chooseApplicationMenuItem") {
this._chooseClientApp((aResult) => {
if (!aResult) {
// Select the (per-prefs) selected handler if no application
// was selected
this._setSelectedHandler(this._getFeedType());
}
this._chooseClientApp(() => {
// Select the (per-prefs) selected handler if no application
// was selected
LOG("Selected handler after callback");
this._setAlwaysUseLabel();
});
} else {
this._setAlwaysUseLabel();
@ -687,28 +581,18 @@ FeedWriter.prototype = {
},
_getWebHandlerElementsForURL(aURL) {
let menu = this._document.getElementById("handlersMenuList");
return menu.querySelectorAll('[webhandlerurl="' + aURL + '"]');
return this._handlersList.querySelectorAll('[webhandlerurl="' + aURL + '"]');
},
_setSelectedHandler(feedType) {
let prefs = Services.prefs;
let handler = prefs.getCharPref(getPrefReaderForType(feedType), "bookmarks");
_setSelectedHandlerResponse(handler, url) {
LOG(`Selecting handler response ${handler} ${url}`);
switch (handler) {
case "web": {
if (this._handlersList) {
let url;
try {
url = prefs.getStringPref(getPrefWebForType(feedType));
} catch (ex) {
LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
return;
}
let handlers =
this._getWebHandlerElementsForURL(url);
if (handlers.length == 0) {
LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
LOG(`Selected web handler isn't in the menulist ${url}`);
return;
}
@ -729,10 +613,10 @@ FeedWriter.prototype = {
}
},
_initSubscriptionUI() {
let handlersList = this._document.getElementById("handlersMenuList");
if (!handlersList)
_initSubscriptionUI(setupMessage) {
if (!this._handlersList)
return;
LOG("UI init");
let feedType = this._getFeedType();
@ -763,7 +647,7 @@ FeedWriter.prototype = {
menuItem.style.display = "none";
this._selectedAppMenuItem = menuItem;
handlersList.appendChild(this._selectedAppMenuItem);
this._handlersList.appendChild(this._selectedAppMenuItem);
// Create the menuitem for the default reader, but don't show/populate it until
// we get confirmation of what it is from the parent
@ -774,10 +658,7 @@ FeedWriter.prototype = {
menuItem.style.display = "none";
this._defaultHandlerMenuItem = menuItem;
handlersList.appendChild(this._defaultHandlerMenuItem);
this._mm.sendAsyncMessage("FeedWriter:RequestClientAppName",
{ feedTypePref: getPrefAppForType(feedType) });
this._handlersList.appendChild(this._defaultHandlerMenuItem);
// "Choose Application..." menuitem
menuItem = liveBookmarksMenuItem.cloneNode(false);
@ -785,18 +666,14 @@ FeedWriter.prototype = {
menuItem.setAttribute("id", "chooseApplicationMenuItem");
menuItem.textContent = this._getString("chooseApplicationMenuItem");
handlersList.appendChild(menuItem);
this._handlersList.appendChild(menuItem);
// separator
let chooseAppSep = liveBookmarksMenuItem.nextElementSibling.cloneNode(false);
chooseAppSep.textContent = liveBookmarksMenuItem.nextElementSibling.textContent;
handlersList.appendChild(chooseAppSep);
this._handlersList.appendChild(chooseAppSep);
// List of web handlers
let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
let handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
for (let handler of handlers) {
for (let handler of setupMessage.handlers) {
if (!handler.uri) {
LOG("Handler with name " + handler.name + " has no URI!? Skipping...");
continue;
@ -807,29 +684,37 @@ FeedWriter.prototype = {
menuItem.textContent = handler.name;
menuItem.setAttribute("handlerType", "web");
menuItem.setAttribute("webhandlerurl", handler.uri);
handlersList.appendChild(menuItem);
this._handlersList.appendChild(menuItem);
}
this._setSelectedHandler(feedType);
this._setSelectedHandlerResponse(setupMessage.reader.handler, setupMessage.reader.url);
if (setupMessage.defaultMenuItem) {
LOG(`Setting default menu item ${setupMessage.defaultMenuItem}`);
this._setApplicationLauncherMenuItem(this._defaultHandlerMenuItem, setupMessage.defaultMenuItem);
}
if (setupMessage.selectedMenuItem) {
LOG(`Setting selected menu item ${setupMessage.selectedMenuItem}`);
this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, setupMessage.selectedMenuItem);
}
// "Subscribe using..."
this._setSubscribeUsingLabel();
// "Always use..." checkbox initial state
this._setAlwaysUseCheckedState(feedType);
this._setCheckboxCheckedState(setupMessage.reader.alwaysUse);
this._setAlwaysUseLabel();
// We update the "Always use.." checkbox label whenever the selected item
// in the list is changed
handlersList.addEventListener("change", this);
this._handlersList.addEventListener("change", this);
// Set up the "Subscribe Now" button
this._document.getElementById("subscribeButton")
.addEventListener("click", this);
// first-run ui
let showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI, true);
if (showFirstRunUI) {
if (setupMessage.showFirstRunUI) {
let textfeedinfo1, textfeedinfo2;
switch (feedType) {
case Ci.nsIFeed.TYPE_VIDEO:
@ -915,53 +800,74 @@ FeedWriter.prototype = {
LOG("Subscribe Preview: feed uri = " + this._window.location.href);
// Set up the subscription UI
this._initSubscriptionUI();
let prefs = Services.prefs;
prefs.addObserver(PREF_SELECTED_ACTION, this, false);
prefs.addObserver(PREF_SELECTED_READER, this, false);
prefs.addObserver(PREF_SELECTED_WEB, this, false);
prefs.addObserver(PREF_SELECTED_APP, this, false);
prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
this._mm.addMessageListener("FeedWriter:PreferenceUpdated", this);
this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
this._mm.addMessageListener("FeedWriter:GetSubscriptionUIResponse", this);
this._mm.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribeResponse", this);
const feedType = this._getFeedType();
this._mm.sendAsyncMessage("FeedWriter:GetSubscriptionUI",
{ feedType });
},
receiveMessage(msg) {
if (!this._window) {
// this._window is null unless this.init was called with a trusted
// window object.
return;
}
LOG(`received message from parent ${msg.name}`);
switch (msg.name) {
case "FeedWriter:SetApplicationLauncherMenuItem":
let menuItem = null;
if (msg.data.type == "DefaultAppMenuItem") {
menuItem = this._defaultHandlerMenuItem;
} else {
// Most likely SelectedAppMenuItem
menuItem = this._selectedAppMenuItem;
case "FeedWriter:PreferenceUpdated":
// This is called when browser-feeds.js spots a pref change
// This will happen when
// - about:preferences#applications changes
// - another feed reader page changes the preference
// - when this page itself changes the select and there isn't a redirect
// bookmarks and launching an external app means the page stays open after subscribe
const feedType = this._getFeedType();
LOG(`Got prefChange! ${JSON.stringify(msg.data)} current type: ${feedType}`);
let feedTypePref = msg.data.default;
if (feedType in msg.data) {
feedTypePref = msg.data[feedType];
}
menuItem.textContent = msg.data.name;
menuItem.style.display = "";
menuItem.selected = true;
LOG(`Got pref ${JSON.stringify(feedTypePref)}`);
this._setCheckboxCheckedState(feedTypePref.alwaysUse);
this._setSelectedHandlerResponse(feedTypePref.handler, feedTypePref.url);
this._setAlwaysUseLabel();
break;
case "FeedWriter:SetFeedPrefsAndSubscribeResponse":
LOG(`FeedWriter:SetFeedPrefsAndSubscribeResponse - Redirecting ${msg.data.redirect}`);
this._window.location.href = msg.data.redirect;
break;
case "FeedWriter:GetSubscriptionUIResponse":
// Set up the subscription UI
this._initSubscriptionUI(msg.data);
break;
case "FeedWriter:SetApplicationLauncherMenuItem":
LOG(`FeedWriter:SetApplicationLauncherMenuItem - picked ${msg.data.name}`);
this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, msg.data.name);
// Potentially a bit racy, but I don't think we can get into a state where this callback is set and
// we're not coming back from ChooseClientApp in browser-feeds.js
if (this._subscribeCallback) {
this._subscribeCallback();
this._subscribeCallback = null;
}
break;
}
},
_setApplicationLauncherMenuItem(menuItem, aName) {
/* unselect all handlers */
[...this._handlersList.children].forEach((option) => {
option.removeAttribute("selected");
});
menuItem.textContent = aName;
menuItem.style.display = "";
menuItem.selected = true;
},
writeContent() {
if (!this._window)
return;
@ -983,24 +889,11 @@ FeedWriter.prototype = {
close() {
this._document.getElementById("subscribeButton")
.removeEventListener("click", this);
this._document.getElementById("handlersMenuList")
this._handlersList
.removeEventListener("change", this);
this._document = null;
this._window = null;
let prefs = Services.prefs;
prefs.removeObserver(PREF_SELECTED_ACTION, this);
prefs.removeObserver(PREF_SELECTED_READER, this);
prefs.removeObserver(PREF_SELECTED_WEB, this);
prefs.removeObserver(PREF_SELECTED_APP, this);
prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
this._handlersList = null;
this._removeFeedFromCache();
this.__bundle = null;
@ -1020,20 +913,6 @@ FeedWriter.prototype = {
}
},
setFeedCharPref(aPrefName, aPrefValue) {
this._mm.sendAsyncMessage("FeedWriter:SetFeedCharPref",
{ pref: aPrefName,
value: aPrefValue });
},
setFeedComplexString(aPrefName, aPrefValue) {
// This sends the string data across to the parent, which will use it in an nsISupportsString
// for a complex value pref.
this._mm.sendAsyncMessage("FeedWriter:SetFeedComplexString",
{ pref: aPrefName,
value: aPrefValue });
},
subscribe() {
let feedType = this._getFeedType();
@ -1041,26 +920,21 @@ FeedWriter.prototype = {
let defaultHandler = "reader";
let useAsDefault = this._document.getElementById("alwaysUse").getAttribute("checked");
let menuList = this._document.getElementById("handlersMenuList");
let selectedItem = menuList.selectedOptions[0];
let selectedItem = this._handlersList.selectedOptions[0];
let subscribeCallback = () => {
let feedReader = null;
let settings = {
feedType,
useAsDefault,
// Pull the title and subtitle out of the document
feedTitle: this._document.getElementById(TITLE_ID).textContent,
feedSubtitle: this._document.getElementById(SUBTITLE_ID).textContent,
feedLocation: this._window.location.href
};
if (selectedItem.hasAttribute("webhandlerurl")) {
let webURI = selectedItem.getAttribute("webhandlerurl");
this.setFeedCharPref(getPrefReaderForType(feedType), "web");
this.setFeedComplexString(getPrefWebForType(feedType), webURI);
let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
let handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
if (handler) {
if (useAsDefault) {
wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
}
this._window.location.href = handler.getHandlerURI(this._window.location.href);
}
feedReader = "web";
settings.uri = selectedItem.getAttribute("webhandlerurl");
} else {
let feedReader = null;
switch (selectedItem.id) {
case "selectedAppMenuItem":
feedReader = "client";
@ -1073,27 +947,20 @@ FeedWriter.prototype = {
feedReader = "bookmarks";
break;
}
this.setFeedCharPref(getPrefReaderForType(feedType), feedReader);
let feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
getService(Ci.nsIFeedResultService);
// Pull the title and subtitle out of the document
let feedTitle = this._document.getElementById(TITLE_ID).textContent;
let feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType, feedReader);
}
settings.reader = feedReader;
// If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
// to either "reader" (If a web reader or if an application is selected),
// or to "bookmarks" (if the live bookmarks option is selected).
// Otherwise, we should set it to "ask"
if (useAsDefault) {
this.setFeedCharPref(getPrefActionForType(feedType), defaultHandler);
} else {
this.setFeedCharPref(getPrefActionForType(feedType), "ask");
if (!useAsDefault) {
defaultHandler = "ask";
}
settings.action = defaultHandler;
LOG(`FeedWriter:SetFeedPrefsAndSubscribe - ${JSON.stringify(settings)}`);
this._mm.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribe",
settings);
}
// Show the file picker before subscribing if the
@ -1111,37 +978,6 @@ FeedWriter.prototype = {
}
},
// nsIObserver
observe(subject, topic, data) {
if (!this._window) {
// this._window is null unless this.init was called with a trusted
// window object.
return;
}
let feedType = this._getFeedType();
if (topic == "nsPref:changed") {
switch (data) {
case PREF_SELECTED_READER:
case PREF_SELECTED_WEB:
case PREF_SELECTED_APP:
case PREF_VIDEO_SELECTED_READER:
case PREF_VIDEO_SELECTED_WEB:
case PREF_VIDEO_SELECTED_APP:
case PREF_AUDIO_SELECTED_READER:
case PREF_AUDIO_SELECTED_WEB:
case PREF_AUDIO_SELECTED_APP:
this._setSelectedHandler(feedType);
break;
case PREF_SELECTED_ACTION:
case PREF_VIDEO_SELECTED_ACTION:
case PREF_AUDIO_SELECTED_ACTION:
this._setAlwaysUseCheckedState(feedType);
}
}
},
get _mm() {
let mm = this._window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDocShell).

Просмотреть файл

@ -42,7 +42,7 @@ const ARRAY_CONTRACTID = "@mozilla.org/array;1";
const SAX_CONTRACTID = "@mozilla.org/saxparser/xmlreader;1";
const PARSERUTILS_CONTRACTID = "@mozilla.org/parserutils;1";
const gMimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
var gIoService = null;
const XMLNS = "http://www.w3.org/XML/1998/namespace";
@ -484,8 +484,15 @@ Entry.prototype = {
if (previous_enc != undefined) {
previous_enc.QueryInterface(Ci.nsIWritablePropertyBag2);
if (!bagHasKey(previous_enc, "type") && bagHasKey(new_enc, "type"))
if (!bagHasKey(previous_enc, "type") && bagHasKey(new_enc, "type")) {
previous_enc.setPropertyAsAString("type", new_enc.getPropertyAsAString("type"));
try {
let handlerInfoWrapper = gMimeService.getFromTypeAndExtension(new_enc.getPropertyAsAString("type"), null);
if (handlerInfoWrapper && handlerInfoWrapper.description) {
previous_enc.setPropertyAsAString("typeDesc", handlerInfoWrapper.description);
}
} catch (ext) {}
}
if (!bagHasKey(previous_enc, "length") && bagHasKey(new_enc, "length"))
previous_enc.setPropertyAsAString("length", new_enc.getPropertyAsAString("length"));