Bug 852828 - Add basic support for subscribing to feeds (RSS/Atom) r=margaret

This commit is contained in:
Mark Finkle 2013-04-03 15:34:51 -04:00
Родитель 61331ffc6c
Коммит c22720d7c0
13 изменённых файлов: 258 добавлений и 39 удалений

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

@ -683,6 +683,20 @@ pref("browser.chrome.dynamictoolbar", true);
pref("webgl.disabled", true);
#endif
// initial web feed readers list
pref("browser.contentHandlers.types.0.title", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.0.uri", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.0.type", "application/vnd.mozilla.maybe.feed");
pref("browser.contentHandlers.types.1.title", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.1.uri", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.1.type", "application/vnd.mozilla.maybe.feed");
pref("browser.contentHandlers.types.2.title", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.2.uri", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.2.type", "application/vnd.mozilla.maybe.feed");
pref("browser.contentHandlers.types.3.title", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.3.uri", "chrome://browser/locale/region.properties");
pref("browser.contentHandlers.types.3.type", "application/vnd.mozilla.maybe.feed");
#ifndef RELEASE_BUILD
// Enable Web Audio for Firefox for Android in Nightly and Aurora
pref("media.webaudio.enabled", true);

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

@ -199,7 +199,7 @@ abstract public class BrowserApp extends GeckoApp
case PAGE_SHOW:
loadFavicon(tab);
break;
case LINK_ADDED:
case LINK_FAVICON:
// If tab is not loading and the favicon is updated, we
// want to load the image straight away. If tab is still
// loading, we only load the favicon once the page's content

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

@ -204,11 +204,15 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
menu.findItem(R.id.share).setVisible(false);
menu.findItem(R.id.add_to_launcher).setVisible(false);
}
if (!tab.getFeedsEnabled()) {
menu.findItem(R.id.subscribe).setVisible(false);
}
} else {
// if there is no tab, remove anything tab dependent
menu.findItem(R.id.copyurl).setVisible(false);
menu.findItem(R.id.share).setVisible(false);
menu.findItem(R.id.add_to_launcher).setVisible(false);
menu.findItem(R.id.subscribe).setVisible(false);
}
}
});

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

@ -2549,6 +2549,19 @@ abstract public class GeckoApp
shareCurrentUrl();
return true;
}
case R.id.subscribe: {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null && tab.getFeedsEnabled()) {
JSONObject args = new JSONObject();
try {
args.put("tabId", tab.getId());
} catch (JSONException e) {
Log.e(LOGTAG, "error building json arguments");
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
}
return true;
}
case R.id.copyurl: {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {

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

@ -38,6 +38,7 @@ public class Tab {
private Bitmap mFavicon;
private String mFaviconUrl;
private int mFaviconSize;
private boolean mFeedsEnabled;
private JSONObject mIdentityData;
private boolean mReaderEnabled;
private BitmapDrawable mThumbnail;
@ -76,6 +77,7 @@ public class Tab {
mFavicon = null;
mFaviconUrl = null;
mFaviconSize = 0;
mFeedsEnabled = false;
mIdentityData = null;
mReaderEnabled = false;
mEnteringReaderMode = false;
@ -189,6 +191,10 @@ public class Tab {
return mFaviconUrl;
}
public boolean getFeedsEnabled() {
return mFeedsEnabled;
}
public String getSecurityMode() {
try {
return mIdentityData.getString("mode");
@ -312,6 +318,10 @@ public class Tab {
mFaviconSize = 0;
}
public void setFeedsEnabled(boolean feedsEnabled) {
mFeedsEnabled = feedsEnabled;
}
public void updateIdentityData(JSONObject identityData) {
mIdentityData = identityData;
}
@ -525,6 +535,7 @@ public class Tab {
setContentType(message.getString("contentType"));
clearFavicon();
setFeedsEnabled(false);
updateTitle(null);
updateIdentityData(null);
setReaderEnabled(false);

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

@ -84,7 +84,8 @@ public class Tabs implements GeckoEventListener {
registerEventListener("Content:PageShow");
registerEventListener("DOMContentLoaded");
registerEventListener("DOMTitleChanged");
registerEventListener("DOMLinkAdded");
registerEventListener("Link:Favicon");
registerEventListener("Link:Feed");
registerEventListener("DesktopMode:Changed");
}
@ -437,9 +438,12 @@ public class Tabs implements GeckoEventListener {
notifyListeners(tab, Tabs.TabEvents.LOADED);
} else if (event.equals("DOMTitleChanged")) {
tab.updateTitle(message.getString("title"));
} else if (event.equals("DOMLinkAdded")) {
} else if (event.equals("Link:Favicon")) {
tab.updateFaviconURL(message.getString("href"), message.getInt("size"));
notifyListeners(tab, TabEvents.LINK_ADDED);
notifyListeners(tab, TabEvents.LINK_FAVICON);
} else if (event.equals("Link:Feed")) {
tab.setFeedsEnabled(true);
notifyListeners(tab, TabEvents.LINK_FEED);
} else if (event.equals("DesktopMode:Changed")) {
tab.setDesktopMode(message.getBoolean("desktopMode"));
notifyListeners(tab, TabEvents.DESKTOP_MODE_CHANGE);
@ -492,7 +496,8 @@ public class Tabs implements GeckoEventListener {
LOCATION_CHANGE,
MENU_UPDATED,
PAGE_SHOW,
LINK_ADDED,
LINK_FAVICON,
LINK_FEED,
SECURITY_CHANGE,
READER_ENABLED,
DESKTOP_MODE_CHANGE

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

@ -164,6 +164,7 @@ size. -->
<!ENTITY contextmenu_paste "Paste">
<!ENTITY contextmenu_copyurl "Copy Address">
<!ENTITY contextmenu_edit_bookmark "Edit">
<!ENTITY contextmenu_subscribe "Subscribe to Page">
<!ENTITY history_removed "Page removed">

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

@ -14,6 +14,9 @@
<item android:id="@+id/share"
android:title="@string/contextmenu_share"/>
<item android:id="@+id/subscribe"
android:title="@string/contextmenu_subscribe"/>
<item android:id="@+id/copyurl"
android:title="@string/contextmenu_copyurl"/>

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

@ -171,6 +171,7 @@
<string name="contextmenu_paste">&contextmenu_paste;</string>
<string name="contextmenu_copyurl">&contextmenu_copyurl;</string>
<string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
<string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
<string name="history_removed">&history_removed;</string>

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

@ -0,0 +1,140 @@
/* 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/. */
"use strict";
var FeedHandler = {
PREF_CONTENTHANDLERS_BRANCH: "browser.contentHandlers.types.",
TYPE_MAYBE_FEED: "application/vnd.mozilla.maybe.feed",
_contentTypes: null,
getContentHandlers: function fh_getContentHandlers(contentType) {
if (!this._contentTypes)
this.loadContentHandlers();
if (!(contentType in this._contentTypes))
return [];
return this._contentTypes[contentType];
},
loadContentHandlers: function fh_loadContentHandlers() {
this._contentTypes = {};
let kids = Services.prefs.getBranch(this.PREF_CONTENTHANDLERS_BRANCH).getChildList("");
// First get the numbers of the providers by getting all ###.uri prefs
let nums = [];
for (let i = 0; i < kids.length; i++) {
let match = /^(\d+)\.uri$/.exec(kids[i]);
if (!match)
continue;
else
nums.push(match[1]);
}
// Sort them, to get them back in order
nums.sort(function(a, b) { return a - b; });
// Now register them
for (let i = 0; i < nums.length; i++) {
let branch = Services.prefs.getBranch(this.PREF_CONTENTHANDLERS_BRANCH + nums[i] + ".");
let vals = branch.getChildList("");
if (vals.length == 0)
return;
try {
let type = branch.getCharPref("type");
let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
let title = branch.getComplexValue("title", Ci.nsIPrefLocalizedString).data;
if (!(type in this._contentTypes))
this._contentTypes[type] = [];
this._contentTypes[type].push({ contentType: type, uri: uri, name: title });
}
catch(ex) {}
}
},
observe: function fh_observe(aSubject, aTopic, aData) {
if (aTopic === "Feeds:Subscribe") {
let args = JSON.parse(aData);
let tab = BrowserApp.getTabForId(args.tabId);
if (!tab)
return;
let browser = tab.browser;
let feeds = browser.feeds;
if (feeds == null)
return;
// First, let's decide on which feed to subscribe
let feedIndex = -1;
if (feeds.length > 1) {
// JSON for Prompt
let feedResult = {
type: "Prompt:Show",
multiple: false,
selected: [],
listitems: []
};
// Build the list of feeds
for (let i = 0; i < feeds.length; i++) {
let item = {
label: feeds[i].title || feeds[i].href,
isGroup: false,
inGroup: false,
disabled: false,
id: i
};
feedResult.listitems.push(item);
}
feedIndex = JSON.parse(sendMessageToJava(feedResult)).button;
} else {
// Only a single feed on the page
feedIndex = 0;
}
if (feedIndex == -1)
return;
let feedURL = feeds[feedIndex].href;
// Next, we decide on which service to send the feed
let handlers = this.getContentHandlers(this.TYPE_MAYBE_FEED);
if (handlers.length == 0)
return;
// JSON for Prompt
let handlerResult = {
type: "Prompt:Show",
multiple: false,
selected: [],
listitems: []
};
// Build the list of handlers
for (let i = 0; i < handlers.length; ++i) {
let item = {
label: handlers[i].name,
isGroup: false,
inGroup: false,
disabled: false,
id: i
};
handlerResult.listitems.push(item);
}
let handlerIndex = JSON.parse(sendMessageToJava(handlerResult)).button;
if (handlerIndex == -1)
return;
// Merge the handler URL and the feed URL
let readerURL = handlers[handlerIndex].uri;
readerURL = readerURL.replace(/%s/gi, encodeURIComponent(feedURL));
// Open the resultant URL in a new tab
BrowserApp.addTab(readerURL, { parentId: BrowserApp.selectedTab.id });
}
}
};

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

@ -95,6 +95,7 @@ var LazyNotificationGetter = {
["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
].forEach(function (aScript) {
let [name, notifications, script] = aScript;
XPCOMUtils.defineLazyGetter(window, name, function() {
@ -3017,11 +3018,11 @@ Tab.prototype = {
if (!target.href || target.disabled)
return;
// ignore on frames and other documents
// Ignore on frames and other documents
if (target.ownerDocument != this.browser.contentDocument)
return;
// sanitize the rel string
// Sanitize the rel string
let list = [];
if (target.rel) {
list = target.rel.toLowerCase().split(/\s+/);
@ -3032,42 +3033,60 @@ Tab.prototype = {
list.push("[" + rel + "]");
}
// We only care about icon links
if (list.indexOf("[icon]") == -1)
return;
if (list.indexOf("[icon]") != -1) {
// We want to get the largest icon size possible for our UI.
let maxSize = 0;
// We want to get the largest icon size possible for our UI.
let maxSize = 0;
// We use the sizes attribute if available
// see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
if (target.hasAttribute("sizes")) {
let sizes = target.getAttribute("sizes").toLowerCase();
// We use the sizes attribute if available
// see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
if (target.hasAttribute("sizes")) {
let sizes = target.getAttribute("sizes").toLowerCase();
if (sizes == "any") {
// Since Java expects an integer, use -1 to represent icons with sizes="any"
maxSize = -1;
} else {
let tokens = sizes.split(" ");
tokens.forEach(function(token) {
// TODO: check for invalid tokens
let [w, h] = token.split("x");
maxSize = Math.max(maxSize, Math.max(w, h));
});
if (sizes == "any") {
// Since Java expects an integer, use -1 to represent icons with sizes="any"
maxSize = -1;
} else {
let tokens = sizes.split(" ");
tokens.forEach(function(token) {
// TODO: check for invalid tokens
let [w, h] = token.split("x");
maxSize = Math.max(maxSize, Math.max(w, h));
});
}
}
let json = {
type: "Link:Favicon",
tabID: this.id,
href: resolveGeckoURI(target.href),
charset: target.ownerDocument.characterSet,
title: target.title,
rel: list.join(" "),
size: maxSize
};
sendMessageToJava(json);
} else if (list.indexOf("[alternate]") != -1) {
let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
if (!isFeed)
return;
try {
// urlSecurityCeck will throw if things are not OK
ContentAreaUtils.urlSecurityCheck(target.href, target.ownerDocument.nodePrincipal, Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
if (!this.browser.feeds)
this.browser.feeds = [];
this.browser.feeds.push({ href: target.href, title: target.title, type: type });
let json = {
type: "Link:Feed",
tabID: this.id
};
sendMessageToJava(json);
} catch (e) {}
}
let json = {
type: "DOMLinkAdded",
tabID: this.id,
href: resolveGeckoURI(target.href),
charset: target.ownerDocument.characterSet,
title: target.title,
rel: list.join(" "),
size: maxSize
};
sendMessageToJava(json);
break;
}

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

@ -48,6 +48,7 @@ chrome.jar:
content/MasterPassword.js (content/MasterPassword.js)
content/FindHelper.js (content/FindHelper.js)
content/PermissionsHelper.js (content/PermissionsHelper.js)
content/FeedHandler.js (content/FeedHandler.js)
% content branding %content/branding/

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

@ -24,3 +24,10 @@ gecko.handlerService.schemes.mailto.0.name=Yahoo! Mail
gecko.handlerService.schemes.mailto.0.uriTemplate=http://compose.mail.yahoo.com/?To=%s
gecko.handlerService.schemes.mailto.1.name=Gmail
gecko.handlerService.schemes.mailto.1.uriTemplate=https://mail.google.com/mail/?extsrc=mailto&url=%s
# This is the default set of web based feed handlers shown in the reader
# selection UI
browser.contentHandlers.types.0.title=My Yahoo!
browser.contentHandlers.types.0.uri=http://add.my.yahoo.com/rss?url=%s
browser.contentHandlers.types.1.title=Google
browser.contentHandlers.types.1.uri=http://fusion.google.com/add?feedurl=%s