--- a/browser/base/content/test/general/browser_discovery.js
+++ b/browser/base/content/test/general/browser_discovery.js
@@ -29,27 +29,36 @@ var iconDiscoveryTests = [
{ 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)" }
];
function runIconDiscoveryTest() {
- var testCase = iconDiscoveryTests[0];
- var head = doc().getElementById("linkparent");
- var hasSrc = gBrowser.getIcon() != null;
- if (testCase.pass)
- ok(hasSrc, testCase.text);
- else
- ok(!hasSrc, testCase.text);
+ let testCase = iconDiscoveryTests[0];
+ let head = doc().getElementById("linkparent");
- head.removeChild(head.getElementsByTagName("link")[0]);
- iconDiscoveryTests.shift();
- iconDiscovery(); // Run the next test.
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon() != null;
+ }, "wait for icon load to finish", 100, 5)
+ .then(() => {
+ ok(testCase.pass, testCase.text);
+ })
+ .catch(() => {
+ ok(!testCase.pass, testCase.text);
+ })
+ .then(() => {
+ head.removeChild(head.getElementsByTagName("link")[0]);
+ iconDiscoveryTests.shift();
+ iconDiscovery(); // Run the next test.
+ });
}
function iconDiscovery() {
if (iconDiscoveryTests.length) {
setHandlerFunc(runIconDiscoveryTest);
gBrowser.setIcon(gBrowser.selectedTab, null,
Services.scriptSecurityManager.getSystemPrincipal());
@@ -64,16 +73,73 @@ function iconDiscovery() {
if (testCase.pass == undefined)
testCase.pass = true;
link.rel = rel;
link.href = href;
link.type = type;
head.appendChild(link);
} else {
+ richIconDiscovery();
+ }
+}
+
+let richIconDiscoveryTests = [
+ { rel: "apple-touch-icon", text: "apple-touch-icon discovered" },
+ { rel: "apple-touch-icon-precomposed", text: "apple-touch-icon-precomposed discovered" },
+ { rel: "fluid-icon", text: "fluid-icon discovered" },
+ { rel: "unknown-icon", pass: false, text: "unknown icon not discovered" }
+];
+
+function runRichIconDiscoveryTest() {
+ let testCase = richIconDiscoveryTests[0];
+ let head = doc().getElementById("linkparent");
+
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon() != null;
+ }, "wait for icon load to finish", 100, 5)
+ .then(() => {
+ ok(testCase.pass, testCase.text);
+ })
+ .catch(() => {
+ ok(!testCase.pass, testCase.text);
+ })
+ .then(() => {
+ head.removeChild(head.getElementsByTagName("link")[0]);
+ richIconDiscoveryTests.shift();
+ richIconDiscovery(); // Run the next test.
+ });
+}
+
+function richIconDiscovery() {
+ if (richIconDiscoveryTests.length) {
+ setHandlerFunc(runRichIconDiscoveryTest);
+ gBrowser.setIcon(gBrowser.selectedTab, null,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ let testCase = richIconDiscoveryTests[0];
+ let head = doc().getElementById("linkparent");
+ let link = doc().createElement("link");
+
+ let rel = testCase.rel;
+ let rootDir = getRootDirectory(gTestPath);
+ let href = testCase.href || rootDir + "moz.png";
+ let type = testCase.type || "image/png";
+ if (testCase.pass === undefined)
+ testCase.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ head.appendChild(link);
+ } else {
searchDiscovery();
}
}
var searchDiscoveryTests = [
{ text: "rel search discovered" },
{ rel: "SEARCH", text: "rel is case insensitive" },
{ rel: "-search-", pass: false, text: "rel -search- not discovered" },
--- a/browser/base/content/test/general/browser_favicon_change_not_in_document.js
+++ b/browser/base/content/test/general/browser_favicon_change_not_in_document.js
@@ -1,34 +1,45 @@
"use strict";
const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change_not_in_document.html"
+/*
+ * This test tests a link element won't fire DOMLinkChanged/DOMLinkAdded unless
+ * it is added to the DOM. See more details in bug 1083895.
+ *
+ * Note that there is debounce logic in ContentLinkHandler.jsm, adding a new
+ * icon link after the icon parsing timeout will trigger a new icon extraction
+ * cycle. Hence, there should be two favicons loads in this test as it appends
+ * a new link to the DOM in the timeout callback defined in the test HTML page.
+ * However, the not-yet-added link element with href as "http://example.org/other-icon"
+ * should not fire the DOMLinkAdded event, nor should it fire the DOMLinkChanged
+ * event after its href gets updated later.
+ */
add_task(async function() {
let extraTab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
- let tabLoaded = promiseTabLoaded(extraTab);
+ let domLinkAddedFired = 0;
+ let domLinkChangedFired = 0;
+ const linkAddedHandler = event => domLinkAddedFired++;
+ const linkChangedhandler = event => domLinkChangedFired++;
+ gBrowser.addEventListener("DOMLinkAdded", linkAddedHandler);
+ gBrowser.addEventListener("DOMLinkChanged", linkChangedhandler);
extraTab.linkedBrowser.loadURI(TEST_URL);
- let expectedFavicon = "http://example.org/one-icon";
- let haveChanged = PromiseUtils.defer();
- let observer = new MutationObserver(function(mutations) {
- for (let mut of mutations) {
- if (mut.attributeName != "image") {
- continue;
- }
- let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
- if (!imageVal) {
- // The value gets removed because it doesn't load.
- continue;
- }
- is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
- haveChanged.resolve();
- }
- });
- observer.observe(extraTab, {attributes: true});
- await tabLoaded;
- expectedFavicon = "http://example.org/yet-another-icon";
- haveChanged = PromiseUtils.defer();
- await haveChanged.promise;
- observer.disconnect();
+ let expectedFavicon = "http://example.org/yet-another-icon";
+ await promiseTabLoaded(extraTab);
+
+ // Make sure the new added favicon link gets loaded.
+ try {
+ await BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon(extraTab) === expectedFavicon;
+ }, "wait for favicon load to finish", 1000, 5);
+ ok(true, "Should load the added favicon");
+ } catch (e) {
+ ok(false, "Should've loaded the new added favicon.");
+ }
+
+ is(domLinkAddedFired, 2, "Should fire the correct number of DOMLinkAdded event.");
+ is(domLinkChangedFired, 0, "Should not fire any DOMLinkChanged event.");
+
+ gBrowser.removeEventListener("DOMLinkAdded", linkAddedHandler);
+ gBrowser.removeEventListener("DOMLinkChanged", linkChangedhandler);
gBrowser.removeTab(extraTab);
});
-
-
--- a/browser/base/content/test/general/browser_subframe_favicons_not_used.js
+++ b/browser/base/content/test/general/browser_subframe_favicons_not_used.js
@@ -4,15 +4,29 @@ function test() {
waitForExplicitFinish();
let testPath = getRootDirectory(gTestPath);
let tab = BrowserTestUtils.addTab(gBrowser, testPath + "file_bug970276_popup1.html");
tab.linkedBrowser.addEventListener("load", function() {
let expectedIcon = testPath + "file_bug970276_favicon1.ico";
- is(gBrowser.getIcon(tab), expectedIcon, "Correct icon.");
+ let icon;
- gBrowser.removeTab(tab);
-
- finish();
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ BrowserTestUtils.waitForCondition(() => {
+ icon = gBrowser.getIcon(tab);
+ return icon != null;
+ }, "wait for favicon load to finish", 100, 5)
+ .then(() => {
+ is(icon, expectedIcon, "Correct icon.");
+ })
+ .catch(() => {
+ ok(false, "Can't get the correct icon.");
+ })
+ .then(() => {
+ gBrowser.removeTab(tab);
+ finish();
+ });
}, {capture: true, once: true});
}
--- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
@@ -37,22 +37,39 @@ add_task(async function test_sessions_ge
background,
});
let win = await BrowserTestUtils.openNewBrowserWindow();
await BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:mozilla");
await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
let expectedTabs = [];
let tab = win.gBrowser.selectedTab;
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly. If that page doesn't have favicon links, let it timeout.
+ try {
+ await BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon(tab) != null;
+ }, "wait for favicon load to finish", 100, 5);
+ } catch (e) {
+ // This page doesn't have any favicon link, just continue.
+ }
expectedTabs.push(expectedTabInfo(tab, win));
let lastAccessedTimes = new Map();
lastAccessedTimes.set("about:mozilla", tab.lastAccessed);
for (let url of ["about:robots", "about:buildconfig"]) {
tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+ try {
+ await BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon(tab) != null;
+ }, "wait for favicon load to finish", 100, 5);
+ } catch (e) {
+ // This page doesn't have any favicon link, just continue.
+ }
expectedTabs.push(expectedTabInfo(tab, win));
lastAccessedTimes.set(url, tab.lastAccessed);
}
await extension.startup();
// Test with a closed tab.
await BrowserTestUtils.removeTab(tab);
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -229,19 +229,16 @@ let InternalFaviconLoader = {
this.onUnload(win);
}
};
win.addEventListener("unload", unloadHandler, true);
}
let {innerWindowID, currentURI} = browser;
- // Immediately cancel any earlier requests
- this.removeRequestsForInner(innerWindowID);
-
// First we do the actual setAndFetch call:
let loadType = PrivateBrowsingUtils.isWindowPrivate(win)
? PlacesUtils.favicons.FAVICON_LOAD_PRIVATE
: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
let callback = this._makeCompletionCallback(win, innerWindowID);
let request = PlacesUtils.favicons.setAndFetchFaviconForPage(currentURI, uri, false,
loadType, callback, principal);
--- a/browser/components/sessionstore/test/browser_attributes.js
+++ b/browser/components/sessionstore/test/browser_attributes.js
@@ -18,16 +18,23 @@ add_task(async function test() {
// Since we need to test 'activemedia-blocked' attribute.
Services.prefs.setBoolPref(PREF2, true)
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF2));
// Add a new tab with a nice icon.
let tab = BrowserTestUtils.addTab(gBrowser, "about:robots");
await promiseBrowserLoaded(tab.linkedBrowser);
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ await BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon(tab) != null;
+ }, "wait for favicon load to finish", 100, 5);
+
// Check that the tab has 'image' and 'iconLoadingPrincipal' attributes.
ok(tab.hasAttribute("image"), "tab.image exists");
ok(tab.hasAttribute("iconLoadingPrincipal"), "tab.iconLoadingPrincipal exists");
tab.toggleMuteAudio();
// Check that the tab has a 'muted' attribute.
ok(tab.hasAttribute("muted"), "tab.muted exists");
--- a/browser/components/sessionstore/test/browser_label_and_icon.js
+++ b/browser/components/sessionstore/test/browser_label_and_icon.js
@@ -12,16 +12,22 @@ add_task(async function test_label_and_i
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.restore_on_demand", true]],
});
// Create a new tab.
let tab = BrowserTestUtils.addTab(gBrowser, "about:robots");
let browser = tab.linkedBrowser;
await promiseBrowserLoaded(browser);
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ await BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon(tab) != null;
+ }, "wait for favicon load to finish", 100, 5);
// Retrieve the tab state.
await TabStateFlusher.flush(browser);
let state = ss.getTabState(tab);
await promiseRemoveTab(tab);
browser = null;
// Open a new tab to restore into.
--- a/browser/components/sessionstore/test/browser_tabicon_after_bg_tab_crash.js
+++ b/browser/components/sessionstore/test/browser_tabicon_after_bg_tab_crash.js
@@ -17,16 +17,22 @@ const PAGE_URL = `data:text/html,
*/
add_task(async function test_tabicon_after_bg_tab_crash() {
let originalTab = gBrowser.selectedTab;
await BrowserTestUtils.withNewTab({
gBrowser,
url: PAGE_URL,
}, async function(browser) {
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ await BrowserTestUtils.waitForCondition(() => {
+ return gBrowser.getIcon() != null;
+ }, "wait for favicon load to finish", 100, 5);
Assert.equal(browser.mIconURL, FAVICON, "Favicon is correctly set.");
await BrowserTestUtils.switchTab(gBrowser, originalTab);
await BrowserTestUtils.crashBrowser(browser,
false /* shouldShowTabCrashPage */);
Assert.equal(browser.mIconURL, FAVICON,
"Favicon is still set after crash.");
});
});
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -20,45 +20,247 @@ XPCOMUtils.defineLazyModuleGetter(this,
const SIZES_TELEMETRY_ENUM = {
NO_SIZES: 0,
ANY: 1,
DIMENSION: 2,
INVALID: 3,
};
+const FAVICON_PARSING_TIMEOUT = 100;
+const FAVICON_RICH_ICON_MIN_WIDTH = 96;
+
+/*
+ * Create a nsITimer.
+ *
+ * @param {function} aCallback A timeout callback function.
+ * @param {Number} aDelay A timeout interval in millisecond.
+ * @return {nsITimer} A nsITimer object.
+ */
+function setTimeout(aCallback, aDelay) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(aCallback, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+/*
+ * Extract the icon width from the size attribute. It also sends the telemetry
+ * about the size type and size dimension info.
+ *
+ * @param {Array} aSizes An array of strings about size.
+ * @return {Number} A width of the icon in pixel.
+ */
+function extractIconSize(aSizes) {
+ let width = -1;
+ let sizesType;
+ const re = /^([1-9][0-9]*)x[1-9][0-9]*$/i;
+
+ if (aSizes.length) {
+ for (let size of aSizes) {
+ if (size.toLowerCase() == "any") {
+ sizesType = SIZES_TELEMETRY_ENUM.ANY;
+ break;
+ } else {
+ let values = re.exec(size);
+ if (values && values.length > 1) {
+ sizesType = SIZES_TELEMETRY_ENUM.DIMENSION;
+ width = parseInt(values[1]);
+ break;
+ } else {
+ sizesType = SIZES_TELEMETRY_ENUM.INVALID;
+ break;
+ }
+ }
+ }
+ } else {
+ sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
+ }
+
+ // Telemetry probes for measuring the sizes attribute
+ // usage and available dimensions.
+ Services.telemetry.getHistogramById("LINK_ICON_SIZES_ATTR_USAGE").add(sizesType);
+ if (width > 0)
+ Services.telemetry.getHistogramById("LINK_ICON_SIZES_ATTR_DIMENSION").add(width);
+
+ return width;
+}
+
+/*
+ * Get link icon URI from a link dom node.
+ *
+ * @param {DOMNode} aLink A link dom node.
+ * @return {nsIURI} A uri of the icon.
+ */
+function getLinkIconURI(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ let uri = Services.io.newURI(aLink.href, targetDoc.characterSet);
+ try {
+ uri.userPass = "";
+ } catch (e) {
+ // some URIs are immutable
+ }
+ return uri;
+}
+
+/*
+ * Set the icon via sending the "Link:Seticon" message.
+ *
+ * @param {Object} aIconInfo The IconInfo object looks like {
+ * iconUri: icon URI,
+ * loadingPrincipal: icon loading principal
+ * }.
+ * @param {Object} aChromeGlobal A global chrome object.
+ */
+function setIconForLink(aIconInfo, aChromeGlobal) {
+ aChromeGlobal.sendAsyncMessage(
+ "Link:SetIcon",
+ { url: aIconInfo.iconUri.spec, loadingPrincipal: aIconInfo.loadingPrincipal });
+}
+
+/*
+ * Timeout callback function for loading favicon.
+ *
+ * @param {Map} aFaviconLoads A map of page URL and FaviconLoad object pairs,
+ * where the FaviconLoad object looks like {
+ * timer: a nsITimer object,
+ * iconInfos: an array of IconInfo objects
+ * }
+ * @param {String} aPageUrl A page URL string for this callback.
+ * @param {Object} aChromeGlobal A global chrome object.
+ */
+function faviconTimeoutCallback(aFaviconLoads, aPageUrl, aChromeGlobal) {
+ let load = aFaviconLoads.get(aPageUrl);
+ if (!load)
+ return;
+
+ // SVG and ico are the preferred icons
+ let preferredIcon;
+ // Other links with the "icon" tag are the default icons
+ let defaultIcon;
+ // Rich icons are either apple-touch or fluid icons, or the ones of the
+ // dimension 96x96 or greater
+ let largestRichIcon;
+
+ for (let icon of load.iconInfos) {
+ if (icon.type === "image/svg+xml" ||
+ icon.type === "image/x-icon" ||
+ icon.type === "image/vnd.microsoft.icon") {
+ preferredIcon = icon;
+ continue;
+ }
+
+ if (icon.isRichIcon) {
+ if (!largestRichIcon || largestRichIcon.width < icon.width) {
+ largestRichIcon = icon;
+ }
+ } else if (!defaultIcon) {
+ defaultIcon = icon;
+ }
+ }
+
+ // Now set the favicons for the page in the following order:
+ // 1. Set the preferred one if any, otherwise use the default one.
+ // 2. Set the best rich icon if any.
+ if (preferredIcon) {
+ setIconForLink(preferredIcon, aChromeGlobal);
+ } else if (defaultIcon) {
+ setIconForLink(defaultIcon, aChromeGlobal);
+ }
+
+ if (largestRichIcon) {
+ setIconForLink(largestRichIcon, aChromeGlobal);
+ }
+ load.timer = null;
+ aFaviconLoads.delete(aPageUrl);
+}
+
+/*
+ * Favicon link handler.
+ *
+ * @param {DOMNode} aLink A link dom node.
+ * @param {bool} aIsRichIcon A bool to indicate if the link is rich icon.
+ * @param {Object} aChromeGlobal A global chrome object.
+ * @param {Map} aFaviconLoads A map of page URL and FaviconLoad object pairs.
+ * @return {bool} Returns true if the link is successfully handled.
+ */
+function handleFaviconLink(aLink, aIsRichIcon, aChromeGlobal, aFaviconLoads) {
+ let pageUrl = aLink.ownerDocument.documentURI;
+ let iconUri = getLinkIconURI(aLink);
+ if (!iconUri)
+ return false;
+
+ // Extract the size type and width. Note that some sites use hi-res icons
+ // without specifying them as apple-touch or fluid icons.
+ let width = extractIconSize(aLink.sizes);
+ if (width >= FAVICON_RICH_ICON_MIN_WIDTH)
+ aIsRichIcon = true;
+
+ let iconInfo = {
+ iconUri,
+ width,
+ isRichIcon: aIsRichIcon,
+ type: aLink.type,
+ loadingPrincipal: aLink.ownerDocument.nodePrincipal
+ };
+
+ if (aFaviconLoads.has(pageUrl)) {
+ let load = aFaviconLoads.get(pageUrl);
+ load.iconInfos.push(iconInfo)
+ // Re-initialize the timer
+ load.timer.delay = FAVICON_PARSING_TIMEOUT;
+ } else {
+ let timer = setTimeout(() => faviconTimeoutCallback(aFaviconLoads, pageUrl, aChromeGlobal),
+ FAVICON_PARSING_TIMEOUT);
+ let load = { timer, iconInfos: [iconInfo] };
+ aFaviconLoads.set(pageUrl, load);
+ }
+ return true;
+}
+
this.ContentLinkHandler = {
init(chromeGlobal) {
- chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
- this.onLinkEvent(event, chromeGlobal);
+ const faviconLoads = new Map();
+ chromeGlobal.addEventListener("DOMLinkAdded", event => {
+ this.onLinkEvent(event, chromeGlobal, faviconLoads);
+ });
+ chromeGlobal.addEventListener("DOMLinkChanged", event => {
+ this.onLinkEvent(event, chromeGlobal, faviconLoads);
});
- chromeGlobal.addEventListener("DOMLinkChanged", (event) => {
- this.onLinkEvent(event, chromeGlobal);
+ chromeGlobal.addEventListener("unload", event => {
+ for (const [pageUrl, load] of faviconLoads) {
+ load.timer.cancel();
+ load.timer = null;
+ faviconLoads.delete(pageUrl);
+ }
});
},
- onLinkEvent(event, chromeGlobal) {
+ onLinkEvent(event, chromeGlobal, faviconLoads) {
var link = event.originalTarget;
var rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href)
return;
// Ignore sub-frames (bugs 305472, 479408).
let window = link.ownerGlobal;
if (window != window.top)
return;
+ // Note: following booleans only work for the current link, not for the
+ // whole content
var feedAdded = false;
var iconAdded = false;
var searchAdded = false;
var rels = {};
for (let relString of rel.split(/\s+/))
rels[relString] = true;
for (let relVal in rels) {
+ let isRichIcon = true;
+
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)) {
@@ -66,56 +268,25 @@ this.ContentLinkHandler = {
{type: link.type,
href: link.href,
title: link.title});
feedAdded = true;
}
}
break;
case "icon":
+ isRichIcon = false;
+ // Fall through to rich icon handling
+ case "apple-touch-icon":
+ case "apple-touch-icon-precomposed":
+ case "fluid-icon":
if (iconAdded || !Services.prefs.getBoolPref("browser.chrome.site_icons"))
break;
- var uri = this.getLinkIconURI(link);
- if (!uri)
- break;
-
- // Telemetry probes for measuring the sizes attribute
- // usage and available dimensions.
- let sizeHistogramTypes = Services.telemetry.
- getHistogramById("LINK_ICON_SIZES_ATTR_USAGE");
- let sizeHistogramDimension = Services.telemetry.
- getHistogramById("LINK_ICON_SIZES_ATTR_DIMENSION");
- let sizesType;
- if (link.sizes.length) {
- for (let size of link.sizes) {
- if (size.toLowerCase() == "any") {
- sizesType = SIZES_TELEMETRY_ENUM.ANY;
- break;
- } else {
- let re = /^([1-9][0-9]*)x[1-9][0-9]*$/i;
- let values = re.exec(size);
- if (values && values.length > 1) {
- sizesType = SIZES_TELEMETRY_ENUM.DIMENSION;
- sizeHistogramDimension.add(parseInt(values[1]));
- } else {
- sizesType = SIZES_TELEMETRY_ENUM.INVALID;
- break;
- }
- }
- }
- } else {
- sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
- }
- sizeHistogramTypes.add(sizesType);
-
- chromeGlobal.sendAsyncMessage(
- "Link:SetIcon",
- {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
- iconAdded = true;
+ iconAdded = handleFaviconLink(link, isRichIcon, chromeGlobal, faviconLoads);
break;
case "search":
if (!searchAdded && event.type == "DOMLinkAdded") {
var type = link.type && link.type.toLowerCase();
type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
let re = /^(?:https?|ftp):/i;
if (type == "application/opensearchdescription+xml" && link.title &&
@@ -126,20 +297,9 @@ this.ContentLinkHandler = {
url: link.ownerDocument.documentURI});
searchAdded = true;
}
}
break;
}
}
},
-
- getLinkIconURI(aLink) {
- let targetDoc = aLink.ownerDocument;
- var uri = Services.io.newURI(aLink.href, targetDoc.characterSet);
- try {
- uri.userPass = "";
- } catch (e) {
- // some URIs are immutable
- }
- return uri;
- },
};