Backed out changeset d61965849528 (bug 1491243) for en-US failures at testing\firefox-ui\tests\puppeteer\test_page_info_window.py

This commit is contained in:
Coroiu Cristina 2018-09-17 22:23:58 +03:00
parent 3bc9db418d
commit 9a2b88d99e
48 changed files with 1411 additions and 18 deletions

View File

@ -9,6 +9,8 @@ const EXPORTED_SYMBOLS = ["LinkHandlerChild"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
ChromeUtils.defineModuleGetter(this, "Feeds",
"resource:///modules/Feeds.jsm");
ChromeUtils.defineModuleGetter(this, "FaviconLoader",
"resource:///modules/FaviconLoader.jsm");
@ -95,6 +97,7 @@ class LinkHandlerChild extends ActorChild {
// Note: following booleans only work for the current link, not for the
// whole content
let feedAdded = false;
let iconAdded = false;
let searchAdded = false;
let rels = {};
@ -105,6 +108,22 @@ class LinkHandlerChild extends ActorChild {
let isRichIcon = false;
switch (relVal) {
case "feed":
case "alternate":
if (!feedAdded && event.type == "DOMLinkAdded") {
if (!rels.feed && rels.alternate && rels.stylesheet)
break;
if (Feeds.isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
this.mm.sendAsyncMessage("Link:AddFeed", {
type: link.type,
href: link.href,
title: link.title,
});
feedAdded = true;
}
}
break;
case "apple-touch-icon":
case "apple-touch-icon-precomposed":
case "fluid-icon":

View File

@ -10,6 +10,7 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
Feeds: "resource:///modules/Feeds.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
setTimeout: "resource://gre/modules/Timer.jsm",
});
@ -33,6 +34,7 @@ class PageInfoChild extends ActorChild {
let pageInfoData = {metaViewRows: this.getMetaInfo(document),
docInfo: this.getDocumentInfo(document),
feeds: this.getFeedsInfo(document, strings),
windowInfo: this.getWindowInfo(window)};
message.target.sendAsyncMessage("PageInfo:data", pageInfoData);
@ -96,6 +98,36 @@ class PageInfoChild extends ActorChild {
return docInfo;
}
getFeedsInfo(document, strings) {
let feeds = [];
// Get the feeds from the page.
let linkNodes = document.getElementsByTagName("link");
let length = linkNodes.length;
for (let i = 0; i < length; i++) {
let link = linkNodes[i];
if (!link.href) {
continue;
}
let rel = link.rel && link.rel.toLowerCase();
let rels = {};
if (rel) {
for (let relVal of rel.split(/\s+/)) {
rels[relVal] = true;
}
}
if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
let type = Feeds.isValidFeed(link, document.nodePrincipal, "feed" in rels);
if (type) {
type = strings[type] || strings["application/rss+xml"];
feeds.push([link.title, type, link.href]);
}
}
}
return feeds;
}
// Only called once to get the media tab's media elements from the content page.
getMediaInfo(document, window, strings, mm) {
let frameList = this.goThroughFrames(document, window);

View File

@ -114,6 +114,170 @@ function getMimeTypeForFeedType(aFeedType) {
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.
* Builds a menu of unique feeds associated with the page, and if there
* is only one, shows the feed inline in the browser window.
* @param container
* The feed list container (menupopup or subview) to be populated.
* @param isSubview
* Whether we're creating a subview (true) or menu (false/undefined)
* @return true if the menu/subview should be shown, false if there was only
* one feed and the feed should be shown inline in the browser
* window (do not show the menupopup/subview).
*/
buildFeedList(container, isSubview) {
let feeds = gBrowser.selectedBrowser.feeds;
if (!isSubview && feeds == null) {
// XXX hack -- menu opening depends on setting of an "open"
// attribute, and the menu refuses to open if that attribute is
// set (because it thinks it's already open). onpopupshowing gets
// called after the attribute is unset, and it doesn't get unset
// if we return false. so we unset it here; otherwise, the menu
// refuses to work past this point.
container.parentNode.removeAttribute("open");
return false;
}
for (let i = container.childNodes.length - 1; i >= 0; --i) {
let node = container.childNodes[i];
if (isSubview && node.localName == "label")
continue;
container.removeChild(node);
}
if (!feeds || feeds.length <= 1)
return false;
// Build the menu showing the available feed choices for viewing.
let itemNodeType = isSubview ? "toolbarbutton" : "menuitem";
for (let feedInfo of feeds) {
let item = document.createElement(itemNodeType);
let baseTitle = feedInfo.title || feedInfo.href;
item.setAttribute("label", baseTitle);
item.setAttribute("feed", feedInfo.href);
item.setAttribute("tooltiptext", feedInfo.href);
item.setAttribute("crop", "center");
let className = "feed-" + itemNodeType;
if (isSubview) {
className += " subviewbutton";
}
item.setAttribute("class", className);
container.appendChild(item);
}
return true;
},
/**
* Subscribe to a given feed. Called when
* 1. Page has a single feed and user clicks feed icon in location bar
* 2. Page has a single feed and user selects Subscribe menu item
* 3. Page has multiple feeds and user selects from feed icon popup (or subview)
* 4. Page has multiple feeds and user selects from Subscribe submenu
* @param href
* The feed to subscribe to. May be null, in which case the
* event target's feed attribute is examined.
* @param event
* The event this method is handling. Used to decide where
* to open the preview UI. (Optional, unless href is null)
*/
subscribeToFeed(href, event) {
// Just load the feed in the content area to either subscribe or show the
// preview UI
if (!href)
href = event.target.getAttribute("feed");
urlSecurityCheck(href, gBrowser.contentPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
this.loadFeed(href, event);
},
loadFeed(href, event) {
let feeds = gBrowser.selectedBrowser.feeds;
try {
openUILink(href, event, {
ignoreAlt: true,
triggeringPrincipal: gBrowser.contentPrincipal,
});
} finally {
// We might default to a livebookmarks modal dialog,
// so reset that if the user happens to click it again
gBrowser.selectedBrowser.feeds = feeds;
}
},
get _feedMenuitem() {
delete this._feedMenuitem;
return this._feedMenuitem = document.getElementById("subscribeToPageMenuitem");
},
get _feedMenupopup() {
delete this._feedMenupopup;
return this._feedMenupopup = document.getElementById("subscribeToPageMenupopup");
},
/**
* Update the browser UI to show whether or not feeds are available when
* a page is loaded or the user switches tabs to a page that has feeds.
*/
updateFeeds() {
if (this._updateFeedTimeout)
clearTimeout(this._updateFeedTimeout);
let feeds = gBrowser.selectedBrowser.feeds;
let haveFeeds = feeds && feeds.length > 0;
let feedButton = document.getElementById("feed-button");
if (feedButton) {
if (haveFeeds) {
feedButton.removeAttribute("disabled");
} else {
feedButton.setAttribute("disabled", "true");
}
}
if (!haveFeeds) {
this._feedMenuitem.setAttribute("disabled", "true");
this._feedMenuitem.removeAttribute("hidden");
this._feedMenupopup.setAttribute("hidden", "true");
return;
}
if (feeds.length > 1) {
this._feedMenuitem.setAttribute("hidden", "true");
this._feedMenupopup.removeAttribute("hidden");
} else {
this._feedMenuitem.setAttribute("feed", feeds[0].href);
this._feedMenuitem.removeAttribute("disabled");
this._feedMenuitem.removeAttribute("hidden");
this._feedMenupopup.setAttribute("hidden", "true");
}
},
addFeed(link, browserForLink) {
if (!browserForLink.feeds)
browserForLink.feeds = [];
urlSecurityCheck(link.href, gBrowser.contentPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
let feedURI = makeURI(link.href, document.characterSet);
if (!/^https?$/.test(feedURI.scheme))
return;
browserForLink.feeds.push({ href: link.href, title: link.title });
// If this addition was for the current browser, update the UI. For
// background browsers, we'll update on tab switch.
if (browserForLink == gBrowser.selectedBrowser) {
// Batch updates to avoid updating the UI for multiple onLinkAdded events
// fired within 100ms of each other.
if (this._updateFeedTimeout)
clearTimeout(this._updateFeedTimeout);
this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
}
},
/**
* Get the human-readable display name of a file. This could be the
* application name.

View File

@ -400,6 +400,26 @@
<menuitem id="menu_bookmarkThisPage"
command="Browser:AddBookmarkAs"
key="addBookmarkAsKb"/>
<menuitem id="subscribeToPageMenuitem"
disabled="true"
#ifndef XP_MACOSX
class="menuitem-iconic"
#endif
label="&subscribeToPageMenuitem.label;"
oncommand="return FeedHandler.subscribeToFeed(null, event);"
onclick="checkForMiddleClick(this, event);"
/>
<menu id="subscribeToPageMenupopup"
hidden="true"
#ifndef XP_MACOSX
class="menu-iconic"
#endif
label="&subscribeToPageMenupopup.label;">
<menupopup id="subscribeToPageSubmenuMenupopup"
onpopupshowing="return FeedHandler.buildFeedList(event.target);"
oncommand="return FeedHandler.subscribeToFeed(null, event);"
onclick="checkForMiddleClick(this, event);"/>
</menu>
<menuitem id="menu_bookmarkAllTabs"
label="&addCurPagesCmd.label;"
class="show-only-for-keyboard"

View File

@ -3661,6 +3661,7 @@ var newWindowButtonObserver = {
const DOMEventHandler = {
init() {
let mm = window.messageManager;
mm.addMessageListener("Link:AddFeed", this);
mm.addMessageListener("Link:LoadingIcon", this);
mm.addMessageListener("Link:SetIcon", this);
mm.addMessageListener("Link:SetFailedIcon", this);
@ -3670,6 +3671,11 @@ const DOMEventHandler = {
receiveMessage(aMsg) {
switch (aMsg.name) {
case "Link:AddFeed":
let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title};
FeedHandler.addFeed(link, aMsg.target);
break;
case "Link:LoadingIcon":
if (aMsg.data.canUseForTab) {
this.setPendingIcon(aMsg.target);
@ -4702,6 +4708,9 @@ var XULBrowserWindow = {
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
if (aRequest && aWebProgress.isTopLevel) {
// clear out feed data
browser.feeds = null;
// clear out search-engine data
browser.engines = null;
}
@ -4931,6 +4940,7 @@ var XULBrowserWindow = {
},
asyncUpdateUI() {
FeedHandler.updateFeeds();
BrowserSearch.updateOpenSearchBadge();
},

View File

@ -0,0 +1,60 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
// Via pageInfo.xul -> utilityOverlay.js
/* import-globals-from ../utilityOverlay.js */
/* import-globals-from ./pageInfo.js */
function initFeedTab(feeds) {
for (const [name, type, url] of feeds) {
addRow(name, type, url);
}
const feedListbox = document.getElementById("feedListbox");
document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
}
function addRow(name, type, url) {
const item = document.createXULElement("richlistitem");
const top = document.createXULElement("hbox");
top.setAttribute("flex", "1");
item.appendChild(top);
const bottom = document.createXULElement("hbox");
bottom.setAttribute("flex", "1");
item.appendChild(bottom);
const nameLabel = document.createXULElement("label");
nameLabel.className = "feedTitle";
nameLabel.textContent = name;
nameLabel.setAttribute("flex", "1");
top.appendChild(nameLabel);
const typeLabel = document.createXULElement("label");
typeLabel.textContent = type;
top.appendChild(typeLabel);
const urlContainer = document.createXULElement("hbox");
urlContainer.setAttribute("flex", "1");
bottom.appendChild(urlContainer);
const urlLabel = document.createXULElement("label");
urlLabel.className = "text-link";
urlLabel.textContent = url;
urlLabel.setAttribute("tooltiptext", url);
urlLabel.addEventListener("click", ev => openUILink(this.value, ev, {triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})}));
urlContainer.appendChild(urlLabel);
const subscribeButton = document.createXULElement("button");
subscribeButton.className = "feed-subscribe";
subscribeButton.addEventListener("click",
() => openWebLinkIn(url, "current", { ignoreAlt: true }));
subscribeButton.setAttribute("label", gBundle.getString("feedSubscribe"));
subscribeButton.setAttribute("accesskey", gBundle.getString("feedSubscribe.accesskey"));
bottom.appendChild(subscribeButton);
document.getElementById("feedListbox").appendChild(item);
}

View File

@ -12,6 +12,14 @@
display: none;
}
#feedListbox richlistitem {
-moz-box-orient: vertical;
}
#feedListbox richlistitem:not([selected="true"]) .feed-subscribe {
display: none;
}
groupbox[closed="true"] > .groupbox-body {
visibility: collapse;
}

View File

@ -7,7 +7,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
/* import-globals-from ../../../../toolkit/content/globalOverlay.js */
/* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
/* import-globals-from ../../../../toolkit/content/treeUtils.js */
/* import-globals-from ../utilityOverlay.js */
/* import-globals-from feeds.js */
/* import-globals-from permissions.js */
/* import-globals-from security.js */
@ -333,6 +333,12 @@ function loadPageInfo(frameOuterWindowID, imageElement, browser) {
browser = browser || window.opener.gBrowser.selectedBrowser;
let mm = browser.messageManager;
gStrings["application/rss+xml"] = gBundle.getString("feedRss");
gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
gStrings["text/xml"] = gBundle.getString("feedXML");
gStrings["application/xml"] = gBundle.getString("feedXML");
gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
let imageInfo = imageElement;
// Look for pageInfoListener in content.js. Sends message to listener with arguments.
@ -340,7 +346,7 @@ function loadPageInfo(frameOuterWindowID, imageElement, browser) {
let pageInfoData;
// Get initial pageInfoData needed to display the general, permission and security tabs.
// Get initial pageInfoData needed to display the general, feeds, permission and security tabs.
mm.addMessageListener("PageInfo:data", function onmessage(message) {
mm.removeMessageListener("PageInfo:data", onmessage);
pageInfoData = message.data;
@ -359,6 +365,7 @@ function loadPageInfo(frameOuterWindowID, imageElement, browser) {
document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
makeGeneralTab(pageInfoData.metaViewRows, docInfo);
initFeedTab(pageInfoData.feeds);
onLoadPermission(uri, principal);
securityOnLoad(uri, windowInfo);
});
@ -402,6 +409,11 @@ function resetPageInfo(args) {
gImageView.clear();
gImageHash = {};
/* Reset Feeds Tab */
var feedListbox = document.getElementById("feedListbox");
while (feedListbox.firstChild)
feedListbox.firstChild.remove();
/* Call registered overlay reset functions */
onResetRegistry.forEach(function(func) { func(); });
@ -423,6 +435,7 @@ function doHelpButton() {
const helpTopics = {
"generalPanel": "pageinfo_general",
"mediaPanel": "pageinfo_media",
"feedPanel": "pageinfo_feed",
"permPanel": "pageinfo_permissions",
"securityPanel": "pageinfo_security",
};

View File

@ -32,6 +32,7 @@
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
<script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
<script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
<script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
<script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
@ -73,6 +74,8 @@
oncommand="showTab('general');"/>
<radio id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"
oncommand="showTab('media');" hidden="true"/>
<radio id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"
oncommand="showTab('feed');" hidden="true"/>
<radio id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"
oncommand="showTab('perm');"/>
<radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
@ -261,6 +264,11 @@
</hbox>
</vbox>
<!-- Feeds -->
<vbox id="feedPanel">
<richlistbox id="feedListbox" flex="1"/>
</vbox>
<!-- Permissions -->
<vbox id="permPanel">
<hbox id="permHostBox">

View File

@ -33,6 +33,7 @@ support-files =
download_page_1.txt
download_page_2.txt
dummy_page.html
feed_tab.html
file_documentnavigation_frameset.html
file_double_close_tab.html
file_fullscreen-window-open.html
@ -95,6 +96,8 @@ skip-if = (verify && !debug && (os == 'win'))
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug406216.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug413915.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug416661.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug417483.js]
@ -335,6 +338,9 @@ subsuite = clipboard
support-files = offlineQuotaNotification.cacheManifest offlineQuotaNotification.html
skip-if = os == "linux" && !debug # bug 1304273
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_feed_discovery.js]
support-files = feed_discovery.html
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_gZipOfflineChild.js]
skip-if = verify
support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^

View File

@ -0,0 +1,68 @@
ChromeUtils.defineModuleGetter(this, "Feeds",
"resource:///modules/Feeds.jsm");
function test() {
var exampleUri = makeURI("http://example.com/");
var principal = Services.scriptSecurityManager.createCodebasePrincipal(exampleUri, {});
function testIsFeed(aTitle, aHref, aType, aKnown) {
var link = {
title: aTitle,
href: aHref,
type: aType,
ownerDocument: {
characterSet: "UTF-8",
},
};
return Feeds.isValidFeed(link, principal, aKnown);
}
var href = "http://example.com/feed/";
var atomType = "application/atom+xml";
var funkyAtomType = " aPPLICAtion/Atom+XML ";
var rssType = "application/rss+xml";
var funkyRssType = " Application/RSS+XML ";
var rdfType = "application/rdf+xml";
var texmlType = "text/xml";
var appxmlType = "application/xml";
var noRss = "Foo";
var rss = "RSS";
// things that should be valid
ok(testIsFeed(noRss, href, atomType, false) == atomType,
"detect Atom feed");
ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType,
"clean up and detect Atom feed");
ok(testIsFeed(noRss, href, rssType, false) == rssType,
"detect RSS feed");
ok(testIsFeed(noRss, href, funkyRssType, false) == rssType,
"clean up and detect RSS feed");
// things that should not be feeds
ok(testIsFeed(noRss, href, rdfType, false) == null,
"should not detect RDF non-feed");
ok(testIsFeed(rss, href, rdfType, false) == null,
"should not detect RDF feed from type and title");
ok(testIsFeed(noRss, href, texmlType, false) == null,
"should not detect text/xml non-feed");
ok(testIsFeed(rss, href, texmlType, false) == null,
"should not detect text/xml feed from type and title");
ok(testIsFeed(noRss, href, appxmlType, false) == null,
"should not detect application/xml non-feed");
ok(testIsFeed(rss, href, appxmlType, false) == null,
"should not detect application/xml feed from type and title");
// security check only, returns cleaned up type or "application/rss+xml"
ok(testIsFeed(noRss, href, atomType, true) == atomType,
"feed security check should return Atom type");
ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType,
"feed security check should return cleaned up Atom type");
ok(testIsFeed(noRss, href, rssType, true) == rssType,
"feed security check should return RSS type");
ok(testIsFeed(noRss, href, funkyRssType, true) == rssType,
"feed security check should return cleaned up RSS type");
ok(testIsFeed(noRss, href, "", true) == rssType,
"feed security check without type should return RSS type");
ok(testIsFeed(noRss, href, "garbage", true) == "garbage",
"feed security check with garbage type should return garbage");
}

View File

@ -0,0 +1,33 @@
const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/feed_discovery.html";
/** Test for Bug 377611 **/
add_task(async function() {
// Open a new tab.
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, URL);
registerCleanupFunction(() => gBrowser.removeCurrentTab());
let browser = gBrowser.selectedBrowser;
await BrowserTestUtils.browserLoaded(browser);
let discovered = browser.feeds;
ok(discovered.length > 0, "some feeds should be discovered");
let feeds = {};
for (let aFeed of discovered) {
feeds[aFeed.href] = true;
}
await ContentTask.spawn(browser, feeds, async function(contentFeeds) {
for (let aLink of content.document.getElementsByTagName("link")) {
// ignore real stylesheets, and anything without an href property
if (aLink.type != "text/css" && aLink.href) {
if (/bogus/i.test(aLink.title)) {
ok(!contentFeeds[aLink.href], "don't discover " + aLink.href);
} else {
ok(contentFeeds[aLink.href], "should discover " + aLink.href);
}
}
}
});
});

View File

@ -0,0 +1,78 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=377611
-->
<head>
<title>Test for feed discovery</title>
<meta charset="utf-8">
<!-- Straight up standard -->
<link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
<link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
<link rel="feed" title="3" href="/3.xml" />
<!-- invalid protocol -->
<link rel="alternate" type="application/atom+xml" title="Bogus non file protocol" href="file://path/1.rss" />
<link rel="alternate" type="application/atom+xml" title="Bogus non feed:http protocol" href="feed:http://path/1.rss" />
<link rel="alternate" type="application/atom+xml" title="Bogus non pcast protocol" href="pcast://path/1.rss" />
<!-- rel is a space-separated list -->
<link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
<link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" />
<link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" />
<link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" />
<link rel="meat feed cake" title="8" href="/8.atom" />
<!-- rel is case-insensitive -->
<link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" />
<link rel="fEEd" title="10" href="/10.atom" />
<!-- type can have leading and trailing whitespace -->
<link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" />
<!-- type is case-insensitive -->
<link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" />
<!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't -->
<link rel="feed stylesheet" title="13" href="/13.atom" />
<!-- hyphens or letters around rel not allowed -->
<link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" />
<link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" />
<link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" />
<!-- don't tolerate text/xml if title includes 'rss' not as a word -->
<link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" />
<!-- don't tolerate application/xml if title includes 'rss' not as a word -->
<link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" />
<!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word -->
<link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" />
<!-- don't tolerate random types -->
<link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" />
<!-- don't find Atom by title -->
<link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" />
<!-- don't find application/rss+xml by title -->
<link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" />
<!-- don't find application/rdf+xml by title -->
<link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" />
<!-- don't find application/xml by title -->
<link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" />
<!-- don't find text/xml by title -->
<link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
<!-- alternate and stylesheet isn't a feed -->
<link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
</head>
<body>
</body>
</html>

View File

@ -1,5 +1,8 @@
[DEFAULT]
[browser_pageInfo.js]
support-files =
../general/feed_tab.html
[browser_pageinfo_firstPartyIsolation.js]
support-files =
image.html

View File

@ -0,0 +1,39 @@
const URI = "https://example.com/browser/browser/base/content/test/pageinfo/feed_tab.html";
function test() {
waitForExplicitFinish();
var pageInfo;
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
URI).then(() => {
Services.obs.addObserver(observer, "page-info-dialog-loaded");
pageInfo = BrowserPageInfo();
});
gBrowser.selectedBrowser.loadURI(URI);
function observer(win, topic, data) {
Services.obs.removeObserver(observer, "page-info-dialog-loaded");
pageInfo.onFinished.push(handlePageInfo);
}
function handlePageInfo() {
ok(pageInfo.document.getElementById("feedTab"), "Feed tab");
let feedListbox = pageInfo.document.getElementById("feedListbox");
ok(feedListbox, "Feed list should exist.");
var feedRowsNum = feedListbox.getRowCount();
is(feedRowsNum, 3, "Number of feeds listed should be correct.");
for (var i = 0; i < feedRowsNum; i++) {
let feedItem = feedListbox.getItemAtIndex(i);
let feedTitle = feedItem.querySelector(".feedTitle");
is(feedTitle.textContent, i + 1, "Feed name should be correct.");
}
pageInfo.close();
gBrowser.removeCurrentTab();
finish();
}
}

View File

@ -105,6 +105,10 @@
list-style-image: var(--sidebars-icon) !important;
}
:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme {
list-style-image: var(--subscribe-icon) !important;
}
:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme {
list-style-image: var(--text_encoding-icon) !important;
}
@ -143,6 +147,7 @@
:root[lwthemeicons~="--synced_tabs-icon"] #sync-button:-moz-lwtheme,
:root[lwthemeicons~="--open_file-icon"] #open-file-button:-moz-lwtheme,
:root[lwthemeicons~="--sidebars-icon"] #sidebar-button:-moz-lwtheme,
:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme,
:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme,
:root[lwthemeicons~="--email_link-icon"] #email-link-button:-moz-lwtheme,
:root[lwthemeicons~="--forget-icon"] #panic-button:-moz-lwtheme {

View File

@ -82,6 +82,7 @@ browser.jar:
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)
content/browser/pageinfo/feeds.js (content/pageinfo/feeds.js)
content/browser/pageinfo/permissions.js (content/pageinfo/permissions.js)
content/browser/pageinfo/security.js (content/pageinfo/security.js)
content/browser/content-refreshblocker.js (content/content-refreshblocker.js)

View File

@ -55,7 +55,7 @@ const kSubviewEvents = [
* The current version. We can use this to auto-add new default widgets as necessary.
* (would be const but isn't because of testing purposes)
*/
var kVersion = 15;
var kVersion = 14;
/**
* Buttons removed from built-ins by version they were removed. kVersion must be
@ -63,7 +63,6 @@ var kVersion = 15;
* version the button is removed in as the value. e.g. "pocket-button": 5
*/
var ObsoleteBuiltinButtons = {
"feed-button": 15,
};
/**

View File

@ -329,6 +329,44 @@ const CustomizableWidgets = [
},
},
{
id: "feed-button",
type: "view",
viewId: "PanelUI-feeds",
tooltiptext: "feed-button.tooltiptext2",
onClick(aEvent) {
let win = aEvent.target.ownerGlobal;
let feeds = win.gBrowser.selectedBrowser.feeds;
// Here, we only care about the case where we have exactly 1 feed and the
// user clicked...
let isClick = (aEvent.button == 0 || aEvent.button == 1);
if (feeds && feeds.length == 1 && isClick) {
aEvent.preventDefault();
aEvent.stopPropagation();
win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent);
CustomizableUI.hidePanelForNode(aEvent.target);
}
},
onViewShowing(aEvent) {
let doc = aEvent.target.ownerDocument;
let container = doc.getElementById("PanelUI-feeds");
let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true);
// For no feeds or only a single one, don't show the panel.
if (!gotView) {
aEvent.preventDefault();
aEvent.stopPropagation();
}
},
onCreated(node) {
let win = node.ownerGlobal;
let selectedBrowser = win.gBrowser.selectedBrowser;
let feeds = selectedBrowser && selectedBrowser.feeds;
if (!feeds || !feeds.length) {
node.setAttribute("disabled", "true");
}
},
}, {
id: "characterencoding-button",
label: "characterencoding-button2.label",
type: "view",

View File

@ -2,6 +2,8 @@
support-files =
head.js
support/test_967000_charEncoding_page.html
support/feeds_test_page.html
support/test-feed.xml
[browser_694291_searchbar_preference.js]
[browser_873501_handle_specials.js]
@ -93,6 +95,8 @@ skip-if = verify
skip-if = verify
[browser_963639_customizing_attribute_non_customizable_toolbar.js]
[browser_967000_button_charEncoding.js]
[browser_967000_button_feeds.js]
skip-if = (verify && debug && (os == 'linux'))
[browser_968565_insert_before_hidden_items.js]
[browser_969427_recreate_destroyed_widget_after_reset.js]
[browser_969661_character_encoding_navbar_disabled.js]

View File

@ -7,7 +7,7 @@
// don't try this at home, kids.
function test() {
// Customize something to make sure stuff changed:
CustomizableUI.addWidgetToArea("save-page-button", CustomizableUI.AREA_NAVBAR);
CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
// Check what version we're on:
let CustomizableUIBSPass = ChromeUtils.import("resource:///modules/CustomizableUI.jsm", {});

View File

@ -4,7 +4,7 @@
// don't try this at home, kids.
function test() {
// Customize something to make sure stuff changed:
CustomizableUI.addWidgetToArea("save-page-button", CustomizableUI.AREA_NAVBAR);
CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
let CustomizableUIBSPass = ChromeUtils.import("resource:///modules/CustomizableUI.jsm", {});

View File

@ -5,7 +5,7 @@
"use strict";
const kXULWidgetId = "a-test-button"; // we'll create a button with this ID.
const kAPIWidgetId = "save-page-button";
const kAPIWidgetId = "feed-button";
const kPanel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
const kToolbar = CustomizableUI.AREA_NAVBAR;
const kVisiblePalette = "customization-palette";

View File

@ -8,12 +8,12 @@ CustomizableUI.createWidget({id: "cui-panel-item-to-drag-to", defaultArea: Custo
// Dragging an item from the palette to another button in the panel should work.
add_task(async function() {
await startCustomizing();
let btn = document.getElementById("new-window-button");
let btn = document.getElementById("feed-button");
let placements = getAreaWidgetIds(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
let lastButtonIndex = placements.length - 1;
let lastButton = placements[lastButtonIndex];
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["new-window-button", lastButton]);
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
let lastButtonNode = document.getElementById(lastButton);
simulateItemDrag(btn, lastButtonNode, "start");
assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterInsert);
@ -28,11 +28,11 @@ add_task(async function() {
add_task(async function() {
CustomizableUI.addWidgetToArea("cui-panel-item-to-drag-to", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
await startCustomizing();
let btn = document.getElementById("new-window-button");
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
let placementsAfterAppend = placements.concat(["new-window-button"]);
let placementsAfterAppend = placements.concat(["feed-button"]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
@ -49,12 +49,12 @@ add_task(async function() {
CustomizableUI.removeWidgetFromArea(widgetIds.shift());
}
await startCustomizing();
let btn = document.getElementById("new-window-button");
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
assertAreaPlacements(panel.id, []);
let placementsAfterAppend = ["new-window-button"];
let placementsAfterAppend = ["feed-button"];
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");

View File

@ -29,10 +29,10 @@ add_task(function() {
// Insert, then remove items
add_task(function() {
let currentSet = navbar.currentSet;
let newCurrentSet = currentSet.replace("home-button", "new-window-button,sync-button,home-button");
let newCurrentSet = currentSet.replace("home-button", "feed-button,sync-button,home-button");
navbar.currentSet = newCurrentSet;
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
let feedBtn = document.getElementById("new-window-button");
let feedBtn = document.getElementById("feed-button");
let syncBtn = document.getElementById("sync-button");
ok(feedBtn, "Feed button should have been added.");
ok(syncBtn, "Sync button should have been added.");
@ -54,10 +54,10 @@ add_task(function() {
// Simultaneous insert/remove:
add_task(function() {
let currentSet = navbar.currentSet;
let newCurrentSet = currentSet.replace("home-button", "new-window-button");
let newCurrentSet = currentSet.replace("home-button", "feed-button");
navbar.currentSet = newCurrentSet;
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
let feedBtn = document.getElementById("new-window-button");
let feedBtn = document.getElementById("feed-button");
ok(feedBtn, "Feed button should have been added.");
let homeBtn = document.getElementById("home-button");
ok(!homeBtn, "Home button should have been removed.");

View File

@ -0,0 +1,62 @@
/* 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";
const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/feeds_test_page.html";
const TEST_FEED = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test-feed.xml";
var newTab = null;
var initialLocation = gBrowser.currentURI.spec;
add_task(async function() {
info("Check Subscribe button functionality");
// add the Subscribe button to the panel
CustomizableUI.addWidgetToArea("feed-button",
CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
await waitForOverflowButtonShown();
// check the button's functionality
await document.getElementById("nav-bar").overflowable.show();
let feedButton = document.getElementById("feed-button");
ok(feedButton, "The Subscribe button was added to the Panel Menu");
is(feedButton.getAttribute("disabled"), "true", "The Subscribe button is initially disabled");
let panelHidePromise = promiseOverflowHidden(window);
await document.getElementById("nav-bar").overflowable._panel.hidePopup();
await panelHidePromise;
newTab = gBrowser.selectedTab;
await promiseTabLoadEvent(newTab, TEST_PAGE);
await gCUITestUtils.openMainMenu();
await waitForCondition(() => !feedButton.hasAttribute("disabled"));
ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled");
feedButton.click();
await promiseTabLoadEvent(newTab, TEST_FEED);
is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened");
ok(!isOverflowOpen(), "Panel is closed");
if (isOverflowOpen()) {
panelHidePromise = promiseOverflowHidden(window);
await document.getElementById("nav-bar").overflowable._panel.hidePopup();
await panelHidePromise;
}
});
add_task(async function asyncCleanup() {
// reset the panel UI to the default state
await resetCustomization();
ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
// restore the initial location
BrowserTestUtils.addTab(gBrowser, initialLocation);
gBrowser.removeTab(newTab);
});

View File

@ -79,7 +79,7 @@ add_task(function() {
defaultPlacements: [] });
CustomizableUI.registerArea("area-996899-2", { anchor: "PanelUI-menu-button",
type: CustomizableUI.TYPE_MENU_PANEL,
defaultPlacements: ["new-window-button"] });
defaultPlacements: ["feed-button"] });
} catch (ex) {
exceptionThrown = ex;
}

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>Feeds test page</title>
<link rel="alternate" type="application/rss+xml" href="test-feed.xml" title="Test feed">
</head>
<body>
This is a test page for feeds
</body>
</html>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<updated>2010-08-22T18:30:02Z</updated>
<author>
<name>John Doe</name>
</author>
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
<entry>
<title>Item</title>
<link href="http://example.org/first"/>
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
<updated>2010-08-22T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>

View File

@ -120,6 +120,7 @@ async function runTestWithIcons(icons) {
["synced_tabs", "#sync-button", "sync-button"],
["open_file", "#open-file-button", "open-file-button"],
["sidebars", "#sidebar-button", "sidebar-button"],
["subscribe", "#feed-button", "feed-button"],
["text_encoding", "#characterencoding-button", "characterencoding-button"],
["email_link", "#email-link-button", "email-link-button"],
["forget", "#panic-button", "panic-button"],
@ -192,6 +193,7 @@ add_task(async function test_all_icons() {
["synced_tabs", "fox.svg"],
["open_file", "fox.svg"],
["sidebars", "fox.svg"],
["subscribe", "fox.svg"],
["text_encoding", "fox.svg"],
["email_link", "fox.svg"],
["forget", "fox.svg"],
@ -231,6 +233,7 @@ add_task(async function test_some_icons() {
["synced_tabs", ""],
["open_file", ""],
["sidebars", ""],
["subscribe", ""],
["text_encoding", ""],
["email_link", ""],
["forget", ""],

View File

@ -341,6 +341,12 @@ FeedResultService.prototype = {
subtitle,
feedHandler: "default" });
break;
default:
// fall through
case "bookmarks":
Services.cpmm.sendAsyncMessage("FeedConverter:addLiveBookmark",
{ spec, title });
break;
}
},

View File

@ -68,7 +68,15 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "gCanFrameFeeds",
"browser.feeds.unsafelyFrameFeeds", false);
function FeedWriter() {
this._selectedApp = undefined;
this._selectedAppMenuItem = null;
this._subscribeCallback = null;
this._defaultHandlerMenuItem = null;
Services.telemetry.scalarAdd("browser.feeds.preview_loaded", 1);
XPCOMUtils.defineLazyGetter(this, "_mm",
() => this._window.docShell.messageManager);
}
FeedWriter.prototype = {
@ -138,6 +146,24 @@ FeedWriter.prototype = {
return this._bundle.GetStringFromName(key);
},
_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);
checkbox.dispatchEvent(event);
}
}
},
/**
* Returns a date suitable for displaying in the feed preview.
* If the date cannot be parsed, the return value is "false".
@ -167,6 +193,25 @@ FeedWriter.prototype = {
return this.__dateFormatter;
},
/**
* Returns the feed type.
*/
__feedType: null,
_getFeedType() {
if (this.__feedType != null)
return this.__feedType;
try {
// grab the feed because it's got the feed.type in it.
let container = this._getContainer();
let feed = container.QueryInterface(Ci.nsIFeed);
this.__feedType = feed.type;
return feed.type;
} catch (ex) { }
return Ci.nsIFeed.TYPE_FEED;
},
/**
* Writes the feed title into the preview document.
* @param container
@ -427,6 +472,234 @@ FeedWriter.prototype = {
return container;
},
/**
* Get moz-icon url for a file
* @param file
* A nsIFile object for which the moz-icon:// is returned
* @returns moz-icon url of the given file as a string
*/
_getFileIconURL(file) {
let fph = Services.io.getProtocolHandler("file")
.QueryInterface(Ci.nsIFileProtocolHandler);
let urlSpec = fph.getURLSpecFromFile(file);
return "moz-icon://" + urlSpec + "?size=16";
},
/**
* Displays a prompt from which the user may choose a (client) feed reader.
* @param aCallback the callback method, passes in true if a feed reader was
* selected, false otherwise.
*/
_chooseClientApp(aCallback) {
this._subscribeCallback = aCallback;
this._mm.sendAsyncMessage("FeedWriter:ChooseClientApp",
{ title: this._getString("chooseApplicationDialogTitle"),
feedType: this._getFeedType() });
},
_setSubscribeUsingLabel() {
let stringLabel = "subscribeFeedUsing";
switch (this._getFeedType()) {
case Ci.nsIFeed.TYPE_VIDEO:
stringLabel = "subscribeVideoPodcastUsing";
break;
case Ci.nsIFeed.TYPE_AUDIO:
stringLabel = "subscribeAudioPodcastUsing";
break;
}
let subscribeUsing = this._document.getElementById("subscribeUsingDescription");
let textNode = this._document.createTextNode(this._getString(stringLabel));
subscribeUsing.insertBefore(textNode, subscribeUsing.firstChild);
},
_setAlwaysUseLabel() {
let checkbox = this._document.getElementById("alwaysUse");
if (checkbox && this._handlersList) {
let handlerName = this._handlersList.selectedOptions[0]
.textContent;
let stringLabel = "alwaysUseForFeeds";
switch (this._getFeedType()) {
case Ci.nsIFeed.TYPE_VIDEO:
stringLabel = "alwaysUseForVideoPodcasts";
break;
case Ci.nsIFeed.TYPE_AUDIO:
stringLabel = "alwaysUseForAudioPodcasts";
break;
}
let label = this._getFormattedString(stringLabel, [handlerName]);
let checkboxText = this._document.getElementById("checkboxText");
if (checkboxText.lastChild.nodeType == checkboxText.TEXT_NODE) {
checkboxText.lastChild.textContent = label;
} else {
LOG("FeedWriter._setAlwaysUseLabel: Expected textNode as lastChild of alwaysUse label");
let textNode = this._document.createTextNode(label);
checkboxText.appendChild(textNode);
}
}
},
// EventListener
handleEvent(event) {
if (event.target.ownerDocument != this._document) {
LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
return;
}
switch (event.type) {
case "click":
if (event.target.id == "subscribeButton") {
this.subscribe();
}
break;
case "change":
LOG("Change fired");
if (event.target.selectedOptions[0].id == "chooseApplicationMenuItem") {
this._chooseClientApp(() => {
// Select the (per-prefs) selected handler if no application
// was selected
LOG("Selected handler after callback");
this._setAlwaysUseLabel();
});
} else {
this._setAlwaysUseLabel();
}
break;
}
},
_setSelectedHandlerResponse(handler) {
LOG(`Selecting handler response ${handler}`);
switch (handler) {
case "client":
case "default":
// do nothing, these are handled by the onchange event
break;
case "bookmarks":
default: {
let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
if (liveBookmarksMenuItem)
liveBookmarksMenuItem.selected = true;
}
}
},
_initSubscriptionUI(setupMessage) {
if (!this._handlersList)
return;
LOG("UI init");
let feedType = this._getFeedType();
// change the background
let header = this._document.getElementById("feedHeader");
switch (feedType) {
case Ci.nsIFeed.TYPE_VIDEO:
header.className = "videoPodcastBackground";
break;
case Ci.nsIFeed.TYPE_AUDIO:
header.className = "audioPodcastBackground";
break;
default:
header.className = "feedBackground";
}
let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
// Last-selected application
let menuItem = liveBookmarksMenuItem.cloneNode(false);
menuItem.removeAttribute("selected");
menuItem.setAttribute("id", "selectedAppMenuItem");
menuItem.setAttribute("handlerType", "client");
// Hide the menuitem until we select an app
menuItem.style.display = "none";
this._selectedAppMenuItem = menuItem;
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
menuItem = liveBookmarksMenuItem.cloneNode(false);
menuItem.removeAttribute("selected");
menuItem.setAttribute("id", "defaultHandlerMenuItem");
menuItem.setAttribute("handlerType", "client");
menuItem.style.display = "none";
this._defaultHandlerMenuItem = menuItem;
this._handlersList.appendChild(this._defaultHandlerMenuItem);
// "Choose Application..." menuitem
menuItem = liveBookmarksMenuItem.cloneNode(false);
menuItem.removeAttribute("selected");
menuItem.setAttribute("id", "chooseApplicationMenuItem");
menuItem.textContent = this._getString("chooseApplicationMenuItem");
this._handlersList.appendChild(menuItem);
this._setSelectedHandlerResponse(setupMessage.reader.handler);
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._setCheckboxCheckedState(setupMessage.reader.alwaysUse);
this._setAlwaysUseLabel();
// We update the "Always use.." checkbox label whenever the selected item
// in the list is changed
this._handlersList.addEventListener("change", this);
// Set up the "Subscribe Now" button
this._document.getElementById("subscribeButton")
.addEventListener("click", this);
// first-run ui
if (setupMessage.showFirstRunUI) {
let textfeedinfo1, textfeedinfo2;
switch (feedType) {
case Ci.nsIFeed.TYPE_VIDEO:
textfeedinfo1 = "feedSubscriptionVideoPodcast1";
textfeedinfo2 = "feedSubscriptionVideoPodcast2";
break;
case Ci.nsIFeed.TYPE_AUDIO:
textfeedinfo1 = "feedSubscriptionAudioPodcast1";
textfeedinfo2 = "feedSubscriptionAudioPodcast2";
break;
default:
textfeedinfo1 = "feedSubscriptionFeed1";
textfeedinfo2 = "feedSubscriptionFeed2";
}
let feedinfo1 = this._document.getElementById("feedSubscriptionInfo1");
let feedinfo1Str = this._getString(textfeedinfo1);
let feedinfo2 = this._document.getElementById("feedSubscriptionInfo2");
let feedinfo2Str = this._getString(textfeedinfo2);
feedinfo1.textContent = feedinfo1Str;
feedinfo2.textContent = feedinfo2Str;
header.setAttribute("firstrun", "true");
this._mm.sendAsyncMessage("FeedWriter:ShownFirstRun");
}
},
/**
* Returns the original URI object of the feed and ensures that this
* component is only ever invoked from the preview document.
@ -462,6 +735,7 @@ FeedWriter.prototype = {
_document: null,
_feedURI: null,
_feedPrincipal: null,
_handlersList: null,
// BrowserFeedWriter WebIDL methods
init(aWindow) {
@ -475,10 +749,73 @@ FeedWriter.prototype = {
this._window = window;
this._document = window.document;
this._handlersList = this._document.getElementById("handlersMenuList");
this._feedPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(this._feedURI, {});
LOG("Subscribe Preview: feed uri = " + this._window.location.href);
this._mm.addMessageListener("FeedWriter:PreferenceUpdated", this);
this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
this._mm.addMessageListener("FeedWriter:GetSubscriptionUIResponse", 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:PreferenceUpdated":
// This is called when browser-feeds.js spots a pref change
// This will happen when
// - about:preferences#general 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];
}
LOG(`Got pref ${JSON.stringify(feedTypePref)}`);
this._setCheckboxCheckedState(feedTypePref.alwaysUse);
this._setSelectedHandlerResponse(feedTypePref.handler);
this._setAlwaysUseLabel();
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() {
@ -503,12 +840,21 @@ FeedWriter.prototype = {
if (!this._window) {
return;
}
this._document.getElementById("subscribeButton")
.removeEventListener("click", this);
this._handlersList
.removeEventListener("change", this);
this._document = null;
this._window = null;
this._handlersList = null;
this._removeFeedFromCache();
this.__bundle = null;
this._feedURI = null;
this._selectedApp = undefined;
this._selectedAppMenuItem = null;
this._defaultHandlerMenuItem = null;
},
_removeFeedFromCache() {
@ -520,6 +866,69 @@ FeedWriter.prototype = {
}
},
subscribe() {
if (!this._window) {
return;
}
let feedType = this._getFeedType();
// Subscribe to the feed using the selected handler and save prefs
let defaultHandler = "reader";
let useAsDefault = this._document.getElementById("alwaysUse").getAttribute("checked");
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,
};
switch (selectedItem.id) {
case "selectedAppMenuItem":
feedReader = "client";
break;
case "defaultHandlerMenuItem":
feedReader = "default";
break;
case "liveBookmarksMenuItem":
defaultHandler = "bookmarks";
feedReader = "bookmarks";
break;
}
settings.reader = feedReader;
// If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
// to either "reader" (If an application is selected),
// or to "bookmarks" (if the live bookmarks option is selected).
// Otherwise, we should set it to "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
// choose application menuitem was chosen using the keyboard
if (selectedItem.id == "chooseApplicationMenuItem") {
this._chooseClientApp(aResult => {
if (aResult) {
selectedItem =
this._handlersList.selectedOptions[0];
subscribeCallback();
}
});
} else {
subscribeCallback();
}
},
classID: FEEDWRITER_CID,
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver,
Ci.nsIDOMGlobalPropertyInitializer]),

View File

@ -36,6 +36,18 @@
<p id="feedSubscriptionInfo1" />
<p id="feedSubscriptionInfo2" />
</div>
<div id="feedSubscribeLine">
<label id="subscribeUsingDescription">
<select id="handlersMenuList">
<option id="liveBookmarksMenuItem" selected="true">&feedLiveBookmarks;</option>
<option disabled="true">&#x2501;&#x2501;&#x2501;&#x2501;&#x2501;&#x2501;&#x2501;</option>
</select>
</label>
<label id="checkboxText">
<input type="checkbox" id="alwaysUse" class="alwaysUse" checked="false"/>
</label>
<button id="subscribeButton">&feedSubscribeNow;</button>
</div>
</div>
<div id="feedHeaderContainerSpacer"/>
</div>

View File

@ -1,3 +1,6 @@
[browser_registerProtocolHandler_notification.js]
support-files =
browser_registerProtocolHandler_notification.html
[browser_telemetry_checks.js]
support-files =
valid-feed.xml

View File

@ -0,0 +1,96 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function() {
function getSnapShot() {
return Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, false);
}
const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
const FEED_URI = TEST_PATH + "valid-feed.xml";
// Ensure we don't have any pre-existing telemetry that'd mess up counts in here:
Services.telemetry.clearScalars();
const kScalarPrefix = "browser.feeds.";
const kPreviewLoaded = kScalarPrefix + "preview_loaded";
const kSubscribed = kScalarPrefix + "feed_subscribed";
const kLivemarkCount = kScalarPrefix + "livebookmark_count";
const kLivemarkOpened = kScalarPrefix + "livebookmark_opened";
const kLivemarkItemOpened = kScalarPrefix + "livebookmark_item_opened";
let scalarForContent = gMultiProcessBrowser ? "content" : "parent";
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, FEED_URI);
// Ensure we get telemetry from the content process:
let previewCount = await TestUtils.waitForCondition(() => {
let snapshot = getSnapShot()[scalarForContent];
return snapshot && snapshot[kPreviewLoaded];
});
Assert.equal(previewCount, 1, "Should register the preview in telemetry.");
// Now check subscription. We stub out the actual code for adding the live bookmark,
// because the dialog creates an initial copy of the bookmark when it's opened and
// that's hard to deal with deterministically in tests.
let old = PlacesCommandHook.addLiveBookmark;
let createBMPromise = new Promise(resolve => {
PlacesCommandHook.addLiveBookmark = function(...args) {
resolve(args);
// Return the promise because Feeds.jsm expects a promise:
return createBMPromise;
};
});
registerCleanupFunction(() => PlacesCommandHook.addLiveBookmark = old);
await BrowserTestUtils.synthesizeMouseAtCenter("#subscribeButton", {}, tab.linkedBrowser);
let bmArgs = await createBMPromise;
Assert.deepEqual(bmArgs, [FEED_URI, "Example Feed"], "Should have been trying to subscribe");
let snapshot = getSnapShot();
Assert.equal(snapshot.parent[kSubscribed], 1, "Should have subscribed once.");
// Now manually add a livemark in the menu and one in the bookmarks toolbar:
let livemarks = await Promise.all([
PlacesUtils.livemarks.addLivemark({
parentGuid: PlacesUtils.bookmarks.menuGuid,
feedURI: Services.io.newURI(FEED_URI),
}),
PlacesUtils.livemarks.addLivemark({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
feedURI: Services.io.newURI(FEED_URI),
}),
]);
registerCleanupFunction(async () => {
for (let mark of livemarks) {
await PlacesUtils.livemarks.removeLivemark(mark);
}
});
if (document.getElementById("PersonalToolbar").getAttribute("collapsed") == "true") {
CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
registerCleanupFunction(() => CustomizableUI.setToolbarVisibility("PersonalToolbar", false));
}
// Force updating telemetry:
let {PlacesDBUtils} = ChromeUtils.import("resource://gre/modules/PlacesDBUtils.jsm", {});
await PlacesDBUtils._telemetryForFeeds();
Assert.equal(getSnapShot().parent[kLivemarkCount], 2,
"Should have created two livemarks and counted them.");
info("Waiting for livemark");
// Check we count opening the livemark popup:
let livemarkOnToolbar = await TestUtils.waitForCondition(
() => document.querySelector("#PersonalToolbar .bookmark-item[livemark]"));
let popup = livemarkOnToolbar.querySelector("menupopup");
let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
info("Clicking on livemark");
// Using .click() or .doCommand() doesn't seem to work.
EventUtils.synthesizeMouseAtCenter(livemarkOnToolbar, {});
await popupShownPromise;
Assert.equal(getSnapShot().parent[kLivemarkOpened], 1, "Should count livemark opening");
// And opening an item in the popup:
let item = await TestUtils.waitForCondition(
() => popup.querySelector("menuitem.bookmark-item"));
item.doCommand();
Assert.equal(getSnapShot().parent[kLivemarkItemOpened], 1, "Should count livemark item opening");
popup.hidePopup();
BrowserTestUtils.removeTab(tab);
});

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<updated>2010-08-22T18:30:02Z</updated>
<author>
<name>John Doe</name>
</author>
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
<entry>
<title>Item</title>
<link href="http://example.org/first"/>
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
<updated>2010-08-22T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<updated>2010-08-22T18:30:02Z</updated>
<author>
<name>John Doe</name>
</author>
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
<entry>
<title>Item</title>
<link href="http://example.org/first"/>
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
<updated>2010-08-22T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>

View File

@ -7,6 +7,7 @@ support-files =
bug408328-data.xml
bug436801-data.xml
bug494328-data.xml
bug589543-data.xml
valid-feed.xml
valid-unsniffable-feed.xml
@ -16,5 +17,6 @@ support-files =
bug364677-data.xml^headers^
[test_bug436801.html]
[test_bug494328.html]
[test_bug589543.html]
[test_registerHandler.html]
[test_registerHandler_disabled.html]

View File

@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=589543
-->
<head>
<title>Test feed preview subscribe UI</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589543">Mozilla Bug 589543</a>
<p id="display"><iframe id="testFrame" src="bug589543-data.xml"></iframe></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 589543 **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
var doc = SpecialPowers.wrap($("testFrame")).contentDocument;
var popup = doc.getElementById("handlersMenuList");
isnot(popup, null, "Feed preview should have a handlers popup");
});
addLoadEvent(SimpleTest.finish);
</script>
</pre>
</body>
</html>

View File

@ -194,6 +194,8 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY editThisBookmarkCmd.label "Edit This Bookmark">
<!ENTITY bookmarkThisPageCmd.commandkey "d">
<!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
<!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
<!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
<!ENTITY showAllBookmarks2.label "Show All Bookmarks">
<!ENTITY recentBookmarks.label "Recently Bookmarked">

View File

@ -73,6 +73,9 @@ paste-button.label = Paste
# LOCALIZATION NOTE(paste-button.tooltiptext2): %S is the keyboard shortcut.
paste-button.tooltiptext2 = Paste (%S)
feed-button.label = Subscribe
feed-button.tooltiptext2 = Subscribe to this page
# LOCALIZATION NOTE (characterencoding-button2.label): The \u00ad text at the beginning
# of the string is used to disable auto hyphenation on the button text when it is displayed
# in the menu panel.

View File

@ -44,6 +44,9 @@
<!ENTITY mediaSaveAs2.accesskey "e">
<!ENTITY mediaPreview "Media Preview:">
<!ENTITY feedTab "Feeds">
<!ENTITY feedTab.accesskey "F">
<!ENTITY permTab "Permissions">
<!ENTITY permTab.accesskey "P">
<!ENTITY permissionsFor "Permissions for:">

View File

@ -38,6 +38,12 @@ generalSize=%S KB (%S bytes)
generalMetaTag=Meta (1 tag)
generalMetaTags=Meta (%S tags)
feedRss=RSS
feedAtom=Atom
feedXML=XML
feedSubscribe=Subscribe
feedSubscribe.accesskey=u
securityNoOwner=This website does not supply ownership information.
# LOCALIZATION NOTE (securityVisitsNumber):
# Semi-colon list of plural forms.

View File

@ -6,6 +6,13 @@
var EXPORTED_SYMBOLS = [ "Feeds" ];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
"resource:///modules/BrowserWindowTracker.jsm");
var Feeds = {
// Listeners are added in nsBrowserGlue.js
receiveMessage(aMessage) {
@ -18,6 +25,48 @@ var Feeds = {
aMessage.target);
break;
}
case "FeedConverter:addLiveBookmark": {
let topWindow = BrowserWindowTracker.getTopWindow();
topWindow.PlacesCommandHook.addLiveBookmark(data.spec, data.title)
.catch(Cu.reportError);
break;
}
}
},
/**
* isValidFeed: checks whether the given data represents a valid feed.
*
* @param aLink
* An object representing a feed with title, href and type.
* @param aPrincipal
* The principal of the document, used for security check.
* @param aIsFeed
* Whether this is already a known feed or not, if true only a security
* check will be performed.
*/
isValidFeed(aLink, aPrincipal, aIsFeed) {
if (!aLink || !aPrincipal)
return false;
var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
if (!aIsFeed) {
aIsFeed = (type == "application/rss+xml" ||
type == "application/atom+xml");
}
if (aIsFeed) {
try {
let href = Services.io.newURI(aLink.href, aLink.ownerDocument.characterSet);
BrowserUtils.urlSecurityCheck(href, aPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
return type || "application/rss+xml";
} catch (ex) {
}
}
return null;
},
};

View File

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M3.5 10A2.5 2.5 0 1 0 6 12.5 2.5 2.5 0 0 0 3.5 10zM2 1a1 1 0 0 0 0 2 10.883 10.883 0 0 1 11 11 1 1 0 0 0 2 0A12.862 12.862 0 0 0 2 1zm0 4a1 1 0 0 0 0 2 6.926 6.926 0 0 1 7 7 1 1 0 0 0 2 0 8.9 8.9 0 0 0-9-9z"/>
</svg>

After

Width:  |  Height:  |  Size: 580 B

View File

@ -148,6 +148,7 @@
skin/classic/browser/edit-copy.svg (../shared/icons/edit-copy.svg)
skin/classic/browser/edit-cut.svg (../shared/icons/edit-cut.svg)
skin/classic/browser/edit-paste.svg (../shared/icons/edit-paste.svg)
skin/classic/browser/feed.svg (../shared/icons/feed.svg)
skin/classic/browser/folder.svg (../shared/icons/folder.svg)
skin/classic/browser/forget.svg (../shared/icons/forget.svg)
skin/classic/browser/forward.svg (../shared/icons/forward.svg)

View File

@ -212,6 +212,10 @@ toolbar[brighttext] {
list-style-image: url("chrome://browser/skin/tab.svg");
}
#feed-button {
list-style-image: url("chrome://browser/skin/feed.svg");
}
#characterencoding-button {
list-style-image: url("chrome://browser/skin/characterEncoding.svg");
}

View File

@ -516,6 +516,10 @@
<parameter name="aEvent"/>
<body>
<![CDATA[
// Delete the feeds cache if we're hiding the topmost page
// (as opposed to one of its iframes).
if (this.feeds && aEvent.target == this.contentDocument)
this.feeds = null;
if (!this.docShell || !this.fastFind)
return;
var tabBrowser = this.getTabBrowser();
@ -897,6 +901,9 @@
this._fastFind = null;
this._webBrowserFind = null;
// The feeds cache can keep the document inside this browser alive.
this.feeds = null;
this.lastURI = null;
if (!this.isRemoteBrowser) {