зеркало из https://github.com/mozilla/gecko-dev.git
176 строки
6.1 KiB
JavaScript
176 строки
6.1 KiB
JavaScript
/* 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.ownerDocument.defaultView;
|
|
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);
|
|
|
|
if (uri.scheme == 'blob') {
|
|
// Blob URLs don't work cross process, work around this by sending as a data uri
|
|
let channel = NetUtil.newChannel({
|
|
uri: uri,
|
|
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
|
|
loadUsingSystemPrincipal: true
|
|
});
|
|
let listener = {
|
|
encoded: "",
|
|
bis: null,
|
|
onStartRequest: function(aRequest, aContext) {
|
|
this.bis = Components.classes["@mozilla.org/binaryinputstream;1"]
|
|
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
|
},
|
|
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
|
let spec = "data:" + channel.contentType + ";base64," + this.encoded;
|
|
chromeGlobal.sendAsyncMessage(
|
|
"Link:SetIcon",
|
|
{url: spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
|
|
},
|
|
onDataAvailable: function(request, context, inputStream, offset, count) {
|
|
this.bis.setInputStream(inputStream);
|
|
this.encoded += btoa(this.bis.readBytes(this.bis.available()));
|
|
}
|
|
}
|
|
channel.asyncOpen2(listener);
|
|
} else {
|
|
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;
|
|
},
|
|
};
|