summaryrefslogtreecommitdiffstats
path: root/browser/modules/ContentLinkHandler.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules/ContentLinkHandler.jsm')
-rw-r--r--browser/modules/ContentLinkHandler.jsm147
1 files changed, 147 insertions, 0 deletions
diff --git a/browser/modules/ContentLinkHandler.jsm b/browser/modules/ContentLinkHandler.jsm
new file mode 100644
index 000000000..443cae2da
--- /dev/null
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -0,0 +1,147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "ContentLinkHandler" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+const SIZES_TELEMETRY_ENUM = {
+ NO_SIZES: 0,
+ ANY: 1,
+ DIMENSION: 2,
+ INVALID: 3,
+};
+
+this.ContentLinkHandler = {
+ init: function(chromeGlobal) {
+ chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
+ this.onLinkEvent(event, chromeGlobal);
+ }, false);
+ chromeGlobal.addEventListener("DOMLinkChanged", (event) => {
+ this.onLinkEvent(event, chromeGlobal);
+ }, false);
+ },
+
+ onLinkEvent: function(event, chromeGlobal) {
+ 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;
+
+ 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) {
+ 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)) {
+ chromeGlobal.sendAsyncMessage("Link:AddFeed",
+ {type: link.type,
+ href: link.href,
+ title: link.title});
+ feedAdded = true;
+ }
+ }
+ break;
+ case "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;
+ 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 &&
+ re.test(link.href))
+ {
+ let engine = { title: link.title, href: link.href };
+ chromeGlobal.sendAsyncMessage("Link:AddSearch",
+ {engine: engine,
+ url: link.ownerDocument.documentURI});
+ searchAdded = true;
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ getLinkIconURI: function(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet);
+ try {
+ uri.userPass = "";
+ } catch (e) {
+ // some URIs are immutable
+ }
+ return uri;
+ },
+};