From e8e8de756bebef30b51f7e0158ed346e2fbbbc20 Mon Sep 17 00:00:00 2001 From: "rflint%ryanflint.com" Date: Tue, 2 Oct 2007 17:54:36 +0000 Subject: [PATCH] Bug 396203 - Unify checks for DOMLinkAdded. r/a=mconnor git-svn-id: svn://10.0.0.236/trunk@237088 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/browser/base/content/browser.js | 240 ++++++++++-------- .../browser/base/content/pageinfo/feeds.js | 25 +- mozilla/browser/base/content/tabbrowser.xml | 69 ----- mozilla/browser/base/content/test/Makefile.in | 3 + .../base/content/test/autodiscovery.html | 8 + .../content/test/browser_autodiscovery.js | 105 ++++++++ mozilla/browser/base/content/test/moz.png | Bin 0 -> 580 bytes .../browser/base/content/utilityOverlay.js | 74 ++---- 8 files changed, 298 insertions(+), 226 deletions(-) create mode 100644 mozilla/browser/base/content/test/autodiscovery.html create mode 100644 mozilla/browser/base/content/test/browser_autodiscovery.js create mode 100755 mozilla/browser/base/content/test/moz.png diff --git a/mozilla/browser/base/content/browser.js b/mozilla/browser/base/content/browser.js index 63de19b9492..12e40fd6ff4 100644 --- a/mozilla/browser/base/content/browser.js +++ b/mozilla/browser/base/content/browser.js @@ -935,11 +935,12 @@ function prepareForStartup() // hook up UI through progress listener gBrowser.addProgressListener(window.XULBrowserWindow, Components.interfaces.nsIWebProgress.NOTIFY_ALL); - // Initialize the feedhandler - FeedHandler.init(); + // setup our common DOMLinkAdded listener + gBrowser.addEventListener("DOMLinkAdded", + function (event) { DOMLinkHandler.onLinkAdded(event); }, + false); - // Initialize the searchbar - BrowserSearch.init(); + gBrowser.addEventListener("pagehide", FeedHandler.onPageHide, false); } function delayedStartup() @@ -2647,84 +2648,145 @@ var DownloadsButtonDNDObserver = { } } -const BrowserSearch = { - - /** - * Initialize the BrowserSearch - */ - init: function() { - gBrowser.addEventListener("DOMLinkAdded", - function (event) { BrowserSearch.onLinkAdded(event); }, - false); - }, - - /** - * A new tag has been discovered - check to see if it advertises - * a OpenSearch engine. - */ +const DOMLinkHandler = { onLinkAdded: function(event) { - // XXX this event listener can/should probably be combined with the onLinkAdded - // listener in tabbrowser.xml. See comments in FeedHandler.onLinkAdded(). - const target = event.target; - var etype = target.type; - const searchRelRegex = /(^|\s)search($|\s)/i; - const searchHrefRegex = /^(https?|ftp):\/\//i; - - if (!etype) - return; - - // Bug 349431: If the engine has no suggested title, ignore it rather - // than trying to find an alternative. - if (!target.title) + var link = event.originalTarget; + var rel = link.rel && link.rel.toLowerCase(); + if (!link || !link.ownerDocument || !rel || !link.href) return; - if (etype == "application/opensearchdescription+xml" && - searchRelRegex.test(target.rel) && searchHrefRegex.test(target.href)) - { - const targetDoc = target.ownerDocument; - // Set the attribute of the (first) search-engine button. - var searchButton = document.getAnonymousElementByAttribute(this.getSearchBar(), - "anonid", "searchbar-engine-button"); - if (searchButton) { - var browser = gBrowser.getBrowserForDocument(targetDoc); - // Append the URI and an appropriate title to the browser data. - var iconURL = null; - if (gBrowser.shouldLoadFavIcon(browser.currentURI)) - iconURL = browser.currentURI.prePath + "/favicon.ico"; + var feedAdded = false; + var iconAdded = false; + var searchAdded = false; + var relStrings = rel.split(/\s+/); + var rels = {}; + for (let i = 0; i < relStrings.length; i++) + rels[relStrings[i]] = true; - var hidden = false; - // If this engine (identified by title) is already in the list, add it - // to the list of hidden engines rather than to the main list. - // XXX This will need to be changed when engines are identified by URL; - // see bug 335102. - var searchService = - Components.classes["@mozilla.org/browser/search-service;1"] - .getService(Components.interfaces.nsIBrowserSearchService); - if (searchService.getEngineByName(target.title)) - hidden = true; + for (let relVal in rels) { + switch (relVal) { + case "feed": + case "alternate": + if (!feedAdded) { + if (!rels.feed && rels.alternate && rels.stylesheet) + break; - var engines = []; - if (hidden) { - if (browser.hiddenEngines) - engines = browser.hiddenEngines; - } - else { - if (browser.engines) - engines = browser.engines; - } + var feed = { title: link.title, href: link.href, type: link.type }; + if (isValidFeed(feed, link.ownerDocument.nodePrincipal, rels.feed)) { + FeedHandler.addFeed(feed, link.ownerDocument); + feedAdded = true; + } + } + break; + case "icon": + if (!iconAdded) { + if (!gBrowser.mPrefs.getBoolPref("browser.chrome.site_icons")) + break; - engines.push({ uri: target.href, - title: target.title, - icon: iconURL }); + try { + var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]. + getService(Ci.nsIContentPolicy); + } catch(e) { + break; // Refuse to load if we can't do a security check. + } - if (hidden) { - browser.hiddenEngines = engines; - } - else { - browser.engines = engines; - if (browser == gBrowser || browser == gBrowser.mCurrentBrowser) - this.updateSearchButton(); - } + var targetDoc = link.ownerDocument; + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var uri = ios.newURI(link.href, targetDoc.characterSet, null); + try { + // Verify that the load of this icon is legal. + // error pages can load their favicon, to be on the safe side, + // only allow chrome:// favicons + const aboutNeterr = "about:neterror?"; + if (targetDoc.documentURI.substr(0, aboutNeterr.length) != aboutNeterr || + !uri.schemeIs("chrome")) { + var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]. + getService(Ci.nsIScriptSecurityManager); + ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri, + Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); + } + } catch(e) { + break; + } + + // Security says okay, now ask content policy + if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE, + uri, targetDoc.documentURIObject, + link, link.type, null) + != Ci.nsIContentPolicy.ACCEPT) + break; + + var browserIndex = gBrowser.getBrowserIndexForDocument(targetDoc); + // no browser? no favicon. + if (browserIndex == -1) + break; + + var tab = gBrowser.mTabContainer.childNodes[browserIndex]; + gBrowser.setIcon(tab, link.href); + iconAdded = true; + } + break; + case "search": + if (!searchAdded) { + var type = link.type && link.type.toLowerCase(); + type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); + + if (type == "application/opensearchdescription+xml" && link.title && + /^(https?|ftp):/i.test(link.href)) { + var engine = { title: link.title, href: link.href }; + BrowserSearch.addEngine(engine, link.ownerDocument); + searchAdded = true; + } + } + break; + } + } + } +} + +const BrowserSearch = { + addEngine: function(engine, targetDoc) { + // Set the attribute of the (first) search-engine button. + var searchButton = document.getAnonymousElementByAttribute(this.getSearchBar(), "anonid", + "searchbar-engine-button"); + if (searchButton) { + var browser = gBrowser.getBrowserForDocument(targetDoc); + // Append the URI and an appropriate title to the browser data. + var iconURL = null; + if (gBrowser.shouldLoadFavIcon(browser.currentURI)) + iconURL = browser.currentURI.prePath + "/favicon.ico"; + + var hidden = false; + // If this engine (identified by title) is already in the list, add it + // to the list of hidden engines rather than to the main list. + // XXX This will need to be changed when engines are identified by URL; + // see bug 335102. + var searchService = Cc["@mozilla.org/browser/search-service;1"]. + getService(Ci.nsIBrowserSearchService); + if (searchService.getEngineByName(engine.title)) + hidden = true; + + var engines = []; + if (hidden) { + if (browser.hiddenEngines) + engines = browser.hiddenEngines; + } + else { + if (browser.engines) + engines = browser.engines; + } + + engines.push({ uri: engine.href, + title: engine.title, + icon: iconURL }); + + if (hidden) + browser.hiddenEngines = engines; + else { + browser.engines = engines; + if (browser == gBrowser || browser == gBrowser.mCurrentBrowser) + this.updateSearchButton(); } } }, @@ -5073,16 +5135,6 @@ function convertFromUnicode(charset, str) * and shows UI when they are discovered. */ var FeedHandler = { - /** - * Initialize the Feed Handler - */ - init: function() { - gBrowser.addEventListener("DOMLinkAdded", - function (event) { FeedHandler.onLinkAdded(event); }, - true); - gBrowser.addEventListener("pagehide", FeedHandler.onPageHide, true); - }, - onPageHide: function(event) { var theBrowser = gBrowser.getBrowserForDocument(event.target); if (theBrowser) @@ -5237,25 +5289,9 @@ var FeedHandler = { } } }, - - /** - * A new tag has been discovered - check to see if it advertises - * an RSS feed. - */ - onLinkAdded: function(event) { - // XXX this event listener can/should probably be combined with the onLinkAdded - // listener in tabbrowser.xml, which only listens for favicons and then passes - // them to onLinkIconAvailable in the ProgressListener. We could extend the - // progress listener to have a generic onLinkAvailable and have tabbrowser pass - // along all events. It should give us the browser for the tab, as well as - // the actual event. - - var feed = recognizeFeedFromLink(event.target, - event.target.ownerDocument.nodePrincipal); + addFeed: function(feed, targetDoc) { if (feed) { - const targetDoc = event.target.ownerDocument; - // find which tab this is for, and set the attribute on the browser var browserForLink = gBrowser.getBrowserForDocument(targetDoc); if (!browserForLink) { diff --git a/mozilla/browser/base/content/pageinfo/feeds.js b/mozilla/browser/base/content/pageinfo/feeds.js index e0d5dd28eab..f5f00f3146c 100644 --- a/mozilla/browser/base/content/pageinfo/feeds.js +++ b/mozilla/browser/base/content/pageinfo/feeds.js @@ -50,14 +50,23 @@ function initFeedTab() var linkNodes = gDocument.getElementsByTagName("link"); var length = linkNodes.length; for (var i = 0; i < length; i++) { - var feed = recognizeFeedFromLink(linkNodes[i], gDocument.nodePrincipal); - if (feed) { - var type = feed.type; - if (type in feedTypes) - type = feedTypes[type]; - else - type = feedTypes["application/rss+xml"]; - addRow(feed.title, type, feed.href); + var link = linkNodes[i]; + if (!link.href) + continue; + + var rel = link.rel && link.rel.toLowerCase(); + var rels = {}; + if (rel) { + for each (let relVal in rel.split(/\s+/)) + rels[relVal] = true; + } + + if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) { + var feed = { title: link.title, href: link.href, type: link.type || "" }; + if (isValidFeed(feed, gDocument.nodePrincipal, rels.feed)) { + var type = feedTypes[feed.type] || feedTypes["application/rss+xml"]; + addRow(feed.title, type, feed.href); + } } } diff --git a/mozilla/browser/base/content/tabbrowser.xml b/mozilla/browser/base/content/tabbrowser.xml index 4502b77cb75..333fdc88f49 100644 --- a/mozilla/browser/base/content/tabbrowser.xml +++ b/mozilla/browser/base/content/tabbrowser.xml @@ -853,73 +853,6 @@ - - - - - - - @@ -2400,8 +2333,6 @@ - - + + + Autodiscovery Test + + + + diff --git a/mozilla/browser/base/content/test/browser_autodiscovery.js b/mozilla/browser/base/content/test/browser_autodiscovery.js new file mode 100644 index 00000000000..f1e452ec4ee --- /dev/null +++ b/mozilla/browser/base/content/test/browser_autodiscovery.js @@ -0,0 +1,105 @@ +function url(spec) { + var ios = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + return ios.newURI(spec, null, null); +} + +var gTestPage = null; +function test() { + waitForExplicitFinish(); + var activeWin = Application.activeWindow; + gTestPage = activeWin.open(url("chrome://mochikit/content/browser/browser/base/content/test/autodiscovery.html")); + gTestPage.focus(); + setTimeout(iconDiscovery, 1000); +} + +function iconDiscovery() { + var tests = [ + { text: "rel icon discovered" }, + { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" }, + { rel: "ICON", text: "rel is case insensitive" }, + { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" }, + { href: "moz.png", text: "relative href works" }, + { href: "notthere.png", text: "404'd icon is removed properly" }, + { href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" }, + { type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" } + ]; + + for (let i = 0; i < tests.length; i++) { + gProxyFavIcon.removeAttribute("src"); + + var test = tests[i]; + var head = gTestPage.document.getElementById("linkparent"); + var link = gTestPage.document.createElement("link"); + + var rel = test.rel || "icon"; + var href = test.href || "chrome://mochikit/content/browser/browser/base/content/test/moz.png"; + var type = test.type || "image/png"; + if (test.pass == undefined) + test.pass = true; + + link.rel = rel; + link.href = href; + link.type = type; + head.appendChild(link); + + var hasSrc = gProxyFavIcon.hasAttribute("src"); + if (test.pass) + ok(hasSrc, test.text); + else + ok(!hasSrc, test.text); + + head.removeChild(link); + } + searchDiscovery(); +} + +function searchDiscovery() { + var tests = [ + { text: "rel search discovered" }, + { rel: "SEARCH", text: "rel is case insensitive" }, + { rel: "-search-", pass: false, text: "rel -search- not discovered" }, + { rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" }, + { href: "https://not.mozilla.com", text: "HTTPS ok" }, + { href: "ftp://not.mozilla.com", text: "FTP ok" }, + { href: "data:text/foo,foo", pass: false, text: "data URI not permitted" }, + { href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" }, + { type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" }, + { type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" }, + { type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" }, + { type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" }, + { rel: "search search search", count: 1, text: "only one engine should be added" } + ]; + + for (let i = 0; i < tests.length; i++) { + var test = tests[i]; + var head = gTestPage.document.getElementById("linkparent"); + var link = gTestPage.document.createElement("link"); + + var rel = test.rel || "search"; + var href = test.href || "http://so.not.here.mozilla.com/search.xml"; + var type = test.type || "application/opensearchdescription+xml"; + var title = test.title || i; + if (test.pass == undefined) + test.pass = true; + + link.rel = rel; + link.href = href; + link.type = type; + link.title = title; + head.appendChild(link); + + var browser = gBrowser.getBrowserForDocument(gTestPage.document); + if (browser.engines) { + var hasEngine = (test.count) ? (browser.engines[0].title == title && + browser.engines.length == test.count) : + (browser.engines[0].title == title); + ok(hasEngine, test.text); + browser.engines = null; + } + else + ok(!test.pass, test.text); + } + gTestPage.close(); + finish(); +} diff --git a/mozilla/browser/base/content/test/moz.png b/mozilla/browser/base/content/test/moz.png new file mode 100755 index 0000000000000000000000000000000000000000..769c636340e11f9d2a0b7eb6a84d574dd9563f0c GIT binary patch literal 580 zcmV-K0=xZ*P)?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2X{|^lf1sei(+1UtAY{Ww#jt?wfcn3@ zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv) SYsoAC0000