gecko/toolkit/modules/BrowserUtils.jsm
Nathan Froyd 0220efbab5 Bug 1133201 - part 1 - treat null links in BrowserUtils.linkHasNoReferrer as specifying rel="noreferrer"; r=Gijs,mconley
It turns out that treating null links as being OK for passing along
referrer information means that we now pass referrer information for
plain text "links" that are opened via the context menu.  For referrer
information, we should take a much more conservative approach, and
declare that null links are always treated as if they had
rel="noreferrer".
2015-02-17 12:46:21 -05:00

236 lines
7.8 KiB
JavaScript

/* -*- mode: js; 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/. */
"use strict";
this.EXPORTED_SYMBOLS = [ "BrowserUtils" ];
const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
Cu.import("resource://gre/modules/Services.jsm");
this.BrowserUtils = {
/**
* Prints arguments separated by a space and appends a new line.
*/
dumpLn: function (...args) {
for (let a of args)
dump(a + " ");
dump("\n");
},
/**
* urlSecurityCheck: JavaScript wrapper for checkLoadURIWithPrincipal
* and checkLoadURIStrWithPrincipal.
* If |aPrincipal| is not allowed to link to |aURL|, this function throws with
* an error message.
*
* @param aURL
* The URL a page has linked to. This could be passed either as a string
* or as a nsIURI object.
* @param aPrincipal
* The principal of the document from which aURL came.
* @param aFlags
* Flags to be passed to checkLoadURIStr. If undefined,
* nsIScriptSecurityManager.STANDARD will be passed.
*/
urlSecurityCheck: function(aURL, aPrincipal, aFlags) {
var secMan = Services.scriptSecurityManager;
if (aFlags === undefined) {
aFlags = secMan.STANDARD;
}
try {
if (aURL instanceof Ci.nsIURI)
secMan.checkLoadURIWithPrincipal(aPrincipal, aURL, aFlags);
else
secMan.checkLoadURIStrWithPrincipal(aPrincipal, aURL, aFlags);
} catch (e) {
let principalStr = "";
try {
principalStr = " from " + aPrincipal.URI.spec;
}
catch(e2) { }
throw "Load of " + aURL + principalStr + " denied.";
}
},
/**
* Constructs a new URI, using nsIIOService.
* @param aURL The URI spec.
* @param aOriginCharset The charset of the URI.
* @param aBaseURI Base URI to resolve aURL, or null.
* @return an nsIURI object based on aURL.
*/
makeURI: function(aURL, aOriginCharset, aBaseURI) {
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
},
makeFileURI: function(aFile) {
return Services.io.newFileURI(aFile);
},
makeURIFromCPOW: function(aCPOWURI) {
return Services.io.newURI(aCPOWURI.spec, aCPOWURI.originCharset, null);
},
/**
* Return the current focus element and window. If the current focus
* is in a content process, then this function returns CPOWs
* (cross-process object wrappers) that refer to the focused
* items. Note that calling this function synchronously contacts the
* content process, which may block for a long time.
*
* @param document The document in question.
* @return [focusedElement, focusedWindow]
*/
getFocusSync: function(document) {
let elt = document.commandDispatcher.focusedElement;
var window = document.commandDispatcher.focusedWindow;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (elt instanceof window.XULElement &&
elt.localName == "browser" &&
elt.namespaceURI == XUL_NS &&
elt.getAttribute("remote")) {
[elt, window] = elt.syncHandler.getFocusedElementAndWindow();
}
return [elt, window];
},
/**
* For a given DOM element, returns its position in "screen"
* coordinates. In a content process, the coordinates returned will
* be relative to the left/top of the tab. In the chrome process,
* the coordinates are relative to the user's screen.
*/
getElementBoundingScreenRect: function(aElement) {
let rect = aElement.getBoundingClientRect();
let window = aElement.ownerDocument.defaultView;
// We need to compensate for any iframes that might shift things
// over. We also need to compensate for zooming.
let fullZoom = window.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
rect = {
left: (rect.left + window.mozInnerScreenX) * fullZoom,
top: (rect.top + window.mozInnerScreenY) * fullZoom,
width: rect.width * fullZoom,
height: rect.height * fullZoom
};
return rect;
},
/**
* Given an element potentially within a subframe, calculate the offsets
* up to the top level browser.
*
* @param aTopLevelWindow content window to calculate offsets to.
* @param aElement The element in question.
* @return [targetWindow, offsetX, offsetY]
*/
offsetToTopLevelWindow: function (aTopLevelWindow, aElement) {
let offsetX = 0;
let offsetY = 0;
let element = aElement;
while (element &&
element.ownerDocument &&
element.ownerDocument.defaultView != aTopLevelWindow) {
element = element.ownerDocument.defaultView.frameElement;
let rect = element.getBoundingClientRect();
offsetX += rect.left;
offsetY += rect.top;
}
let win = null;
if (element == aElement)
win = aTopLevelWindow;
else
win = element.contentDocument.defaultView;
return { targetWindow: win, offsetX: offsetX, offsetY: offsetY };
},
onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
// Don't modify non-default targets or targets that aren't in top-level app
// tab docshells (isAppTab will be false for app tab subframes).
if (originalTarget != "" || !isAppTab)
return originalTarget;
// External links from within app tabs should always open in new tabs
// instead of replacing the app tab's page (Bug 575561)
let linkHost;
let docHost;
try {
linkHost = linkURI.host;
docHost = linkNode.ownerDocument.documentURIObject.host;
} catch(e) {
// nsIURI.host can throw for non-nsStandardURL nsIURIs.
// If we fail to get either host, just return originalTarget.
return originalTarget;
}
if (docHost == linkHost)
return originalTarget;
// Special case: ignore "www" prefix if it is part of host string
let [longHost, shortHost] =
linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
if (longHost == "www." + shortHost)
return originalTarget;
return "_blank";
},
/**
* Map the plugin's name to a filtered version more suitable for UI.
*
* @param aName The full-length name string of the plugin.
* @return the simplified name string.
*/
makeNicePluginName: function (aName) {
if (aName == "Shockwave Flash")
return "Adobe Flash";
// Regex checks if aName begins with "Java" + non-letter char
if (/^Java\W/.exec(aName))
return "Java";
// Clean up the plugin name by stripping off parenthetical clauses,
// trailing version numbers or "plugin".
// EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar"
// Do this by first stripping the numbers, etc. off the end, and then
// removing "Plugin" (and then trimming to get rid of any whitespace).
// (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
let newName = aName.replace(/\(.*?\)/g, "").
replace(/[\s\d\.\-\_\(\)]+$/, "").
replace(/\bplug-?in\b/i, "").trim();
return newName;
},
/**
* Return true if linkNode has a rel="noreferrer" attribute.
*
* @param linkNode The <a> element, or null.
* @return a boolean indicating if linkNode has a rel="noreferrer" attribute.
*/
linkHasNoReferrer: function (linkNode) {
// A null linkNode typically means that we're checking a link that wasn't
// provided via an <a> link, like a text-selected URL. Don't leak
// referrer information in this case.
if (!linkNode)
return true;
let rel = linkNode.getAttribute("rel");
if (!rel)
return false;
// The HTML spec says that rel should be split on spaces before looking
// for particular rel values.
let values = rel.split(/[ \t\r\n\f]/);
return values.indexOf('noreferrer') != -1;
},
};