bug 303181 - Remove reliance on focusedWindow and fix race conditions in browser context menus.

bug 284868 - HTTP Referer not sent at all when "saving link as...".
r=mconnor, a=mconnor
This commit is contained in:
jruderman@hmc.edu
2005-08-12 06:55:54 +00:00
parent 80d0f453ed
commit 6eb09afa19
2 changed files with 188 additions and 194 deletions

View File

@@ -24,7 +24,7 @@
# David Hyatt <hyatt@mozilla.org>
# Peter Annema <disttsc@bart.nl>
# Dean Tessman <dean_tessman@hotmail.com>
# Kevin Puetz (puetzk@iastate.edu)
# Kevin Puetz <puetzk@iastate.edu>
# Ben Goodger <ben@netscape.com>
# Pierre Chanial <chanial@noos.fr>
# Jason Eager <jce2@po.cwru.edu>
@@ -35,6 +35,7 @@
# Peter Parente <parente@cs.unc.edu>
# Giorgio Maone <g.maone@informaction.com>
# Tom Germeau <tom.germeau@epigoon.com>
# Jesse Ruderman <jruderman@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -2592,9 +2593,9 @@ var newTabButtonObserver = {
onDrop: function (aEvent, aXferData, aDragSession)
{
var xferData = aXferData.data.split("\n");
var uri = xferData[0] ? xferData[0] : xferData[1];
if (uri)
openNewTabWith(uri, aXferData, aEvent, false);
var url = xferData[0] ? xferData[0] : xferData[1];
if (url)
openNewTabWith(url, null, null, aEvent);
},
getSupportedFlavours: function ()
{
@@ -2623,9 +2624,9 @@ var newWindowButtonObserver = {
onDrop: function (aEvent, aXferData, aDragSession)
{
var xferData = aXferData.data.split("\n");
var uri = xferData[0] ? xferData[0] : xferData[1];
if (uri)
openNewWindowWith(uri, aXferData, null, false);
var url = xferData[0] ? xferData[0] : xferData[1];
if (url)
openNewWindowWith(url, null, null);
},
getSupportedFlavours: function ()
{
@@ -3973,6 +3974,9 @@ function nsContextMenu( xulMenu ) {
this.onMetaDataItem = false;
this.onMathML = false;
this.link = false;
this.linkURL = "";
this.linkURI = null;
this.linkProtocol = null;
this.inFrame = false;
this.hasBGImage = false;
this.isTextSelected = false;
@@ -4040,16 +4044,13 @@ nsContextMenu.prototype = {
this.showItem( "context-savepage", !( this.inDirList || this.isContentSelected || this.onTextInput || this.onLink || this.onImage ));
this.showItem( "context-sendpage", !( this.inDirList || this.isContentSelected || this.onTextInput || this.onLink || this.onImage ));
// Save link depends on whether we're in a link.
// Save+Send link depends on whether we're in a link.
this.showItem( "context-savelink", this.onSaveableLink );
// Save image depends on whether there is one.
this.showItem( "context-saveimage", this.onLoadedImage );
this.showItem( "context-sendimage", this.onImage );
// Send link depends on whether we're in a link.
this.showItem( "context-sendlink", this.onSaveableLink );
// Save+Send image depends on whether we're on an image.
this.showItem( "context-saveimage", this.onLoadedImage );
this.showItem( "context-sendimage", this.onImage );
},
initViewItems : function () {
// View source is always OK, unless in directory listing.
@@ -4070,24 +4071,8 @@ nsContextMenu.prototype = {
#endif
this.showItem( "context-setDesktopBackground", haveSetDesktopBackground && this.onLoadedImage );
if ( haveSetDesktopBackground && this.onLoadedImage ) {
// Disable the Set as Desktop Background menu item if we're still trying
// to load the image or the load failed
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
var disableDesktopBackground = false;
if (("complete" in this.target) && !this.target.complete)
disableSetWallpaper = true;
else if (makeURI(this.target.src).scheme == "javascript")
disableDesktopBackground = true;
else if (this.target instanceof nsIImageLoadingContent) {
var request = this.target.QueryInterface(nsIImageLoadingContent)
.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
if (!request)
disableDesktopBackground = true;
}
this.setItemAttr( "context-setDesktopBackground", "disabled", disableDesktopBackground);
}
if ( haveSetDesktopBackground && this.onLoadedImage )
this.setItemAttr( "context-setDesktopBackground", "disabled", this.disableSetDesktopBackground());
// View Image depends on whether an image was clicked on.
this.showItem( "context-viewimage", this.onImage && !this.onStandaloneImage );
@@ -4189,27 +4174,38 @@ nsContextMenu.prototype = {
this.shouldDisplay = false;
return;
}
// Initialize contextual info.
this.onImage = false;
this.onLoadedImage = false;
this.onImage = false;
this.onLoadedImage = false;
this.onStandaloneImage = false;
this.onMetaDataItem = false;
this.onTextInput = false;
this.onKeywordField = false;
this.imageURL = "";
this.onLink = false;
this.onMathML = false;
this.inFrame = false;
this.hasBGImage = false;
this.bgImageURL = "";
this.onMetaDataItem = false;
this.onTextInput = false;
this.onKeywordField = false;
this.imageURL = "";
this.onLink = false;
this.linkURL = "";
this.linkURI = null;
this.linkProtocol = "";
this.onMathML = false;
this.inFrame = false;
this.hasBGImage = false;
this.bgImageURL = "";
// Remember the node that was clicked.
this.target = node;
// Remember the URL of the document containing the node
// for referrer header and for security checks.
this.docURL = node.ownerDocument.location.href;
// See if the user clicked on an image.
// First, do checks for nodes that never have children.
if ( this.target.nodeType == Node.ELEMENT_NODE ) {
// See if the user clicked on an image.
if ( this.target instanceof Components.interfaces.nsIImageLoadingContent && this.target.currentURI ) {
this.onImage = true;
this.onMetaDataItem = true;
var request = this.target.getRequest( Components.interfaces.nsIImageLoadingContent.CURRENT_REQUEST );
if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
this.onLoadedImage = true;
@@ -4218,7 +4214,6 @@ nsContextMenu.prototype = {
if ( this.target.ownerDocument instanceof ImageDocument)
this.onStandaloneImage = true;
} else if ( this.target instanceof HTMLInputElement ) {
type = this.target.getAttribute("type");
this.onTextInput = this.isTargetATextBox(this.target);
this.onKeywordField = this.isTargetAKeywordField(this.target);
} else if ( this.target instanceof HTMLTextAreaElement ) {
@@ -4271,52 +4266,39 @@ nsContextMenu.prototype = {
}
}
// We have meta data on images.
this.onMetaDataItem = this.onImage;
// See if the user clicked on MathML
const NS_MathML = "http://www.w3.org/1998/Math/MathML";
if ((this.target.nodeType == Node.TEXT_NODE &&
this.target.parentNode.namespaceURI == NS_MathML)
|| (this.target.namespaceURI == NS_MathML))
this.onMathML = true;
// See if the user clicked in a frame.
if ( this.target.ownerDocument != window.content.document ) {
this.inFrame = true;
}
// Bubble out, looking for items of interest
// Second, bubble out, looking for items of interest that can have childen.
// Always pick the innermost link, background image, etc.
const XMLNS = "http://www.w3.org/XML/1998/namespace";
var elem = this.target;
while ( elem ) {
if ( elem.nodeType == Node.ELEMENT_NODE ) {
// Link?
if ( !this.onLink &&
( (elem instanceof HTMLAnchorElement && elem.href) ||
elem instanceof HTMLAreaElement ||
elem instanceof HTMLLinkElement ||
elem.getAttributeNS( "http://www.w3.org/1999/xlink", "type") == "simple" ) ) {
// Clicked on a link.
( (elem instanceof HTMLAnchorElement && elem.href) ||
elem instanceof HTMLAreaElement ||
elem instanceof HTMLLinkElement ||
elem.getAttributeNS( "http://www.w3.org/1999/xlink", "type") == "simple" ) ) {
// Target is a link or a descendant of a link.
this.onLink = true;
this.onMetaDataItem = true;
// Remember corresponding element.
this.link = elem;
this.onMailtoLink = this.isLinkType( "mailto:", this.link );
// Remember if it is saveable.
this.linkURL = this.getLinkURL();
this.linkURI = this.getLinkURI();
this.linkProtocol = this.getLinkProtocol();
this.onMailtoLink = (this.linkProtocol == "mailto");
this.onSaveableLink = this.isLinkSaveable( this.link );
}
// Text input?
if ( !this.onTextInput ) {
// Clicked on a link.
this.onTextInput = this.isTargetATextBox(elem);
}
// Metadata item?
if ( !this.onMetaDataItem ) {
// We currently display metadata on anything which fits
// the below test.
// We display metadata on anything which fits
// the below test, as well as for links and images
// (which set this.onMetaDataItem to true elsewhere)
if ( ( elem instanceof HTMLQuoteElement && elem.cite) ||
( elem instanceof HTMLTableElement && elem.summary) ||
( elem instanceof HTMLModElement &&
@@ -4342,6 +4324,19 @@ nsContextMenu.prototype = {
}
elem = elem.parentNode;
}
// See if the user clicked on MathML
const NS_MathML = "http://www.w3.org/1998/Math/MathML";
if ((this.target.nodeType == Node.TEXT_NODE &&
this.target.parentNode.namespaceURI == NS_MathML)
|| (this.target.namespaceURI == NS_MathML))
this.onMathML = true;
// See if the user clicked in a frame.
if ( this.target.ownerDocument != window.content.document ) {
this.inFrame = true;
}
},
// Returns the computed style attribute for the given element.
getComputedStyle: function( elem, prop ) {
@@ -4352,56 +4347,28 @@ nsContextMenu.prototype = {
var url = elem.ownerDocument.defaultView.getComputedStyle( elem, '' ).getPropertyCSSValue( prop );
return ( url.primitiveType == CSSPrimitiveValue.CSS_URI ) ? url.getStringValue() : null;
},
// Returns true iff clicked on link is saveable.
// Returns true if clicked-on link targets a resource that can be saved.
isLinkSaveable : function ( link ) {
// We don't do the Right Thing for news/snews yet, so turn them off
// until we do.
return !(this.isLinkType( "mailto:" , link ) ||
this.isLinkType( "javascript:" , link ) ||
this.isLinkType( "news:", link ) ||
this.isLinkType( "snews:", link ) );
},
// Returns true iff clicked on link is of type given.
isLinkType : function ( linktype, link ) {
try {
// Test for missing protocol property.
if ( !link.protocol ) {
// We must resort to testing the URL string :-(.
var protocol;
var wrapper = link;
if (wrapper.href) {
protocol = wrapper.href.substr(0, linktype.length);
} else {
protocol = wrapper.getAttributeNS("http://www.w3.org/1999/xlink","href");
if (protocol) {
protocol = protocol.substr(0, linktype.length);
}
}
return protocol.toLowerCase() === linktype;
} else {
// Presume all but javascript: urls are saveable.
return link.protocol.toLowerCase() === linktype;
}
} catch (e) {
// something was wrong with the link,
// so we won't be able to save it anyway
return false;
}
return this.linkProtocol && !(
this.linkProtocol == "mailto" ||
this.linkProtocol == "javascript" ||
this.linkProtocol == "news" ||
this.linkProtocol == "snews" );
},
// Open linked-to URL in a new window.
openLink : function () {
// Determine linked-to URL.
openNewWindowWith(this.linkURL(), this.link, true);
openNewWindowWith(this.linkURL, this.docURL, null);
},
// Open linked-to URL in a new tab.
openLinkInTab : function () {
// Determine linked-to URL.
openNewTabWith(this.linkURL(), this.link, null, true);
openNewTabWith(this.linkURL, this.docURL, null, null);
},
// Open frame in a new tab.
openFrameInTab : function () {
// Determine linked-to URL.
openNewTabWith(this.target.ownerDocument.location.href, null, null, true);
openNewTabWith(this.target.ownerDocument.location.href, null, null, null);
},
// Reload clicked-in frame.
reloadFrame : function () {
@@ -4409,9 +4376,9 @@ nsContextMenu.prototype = {
},
// Open clicked-in frame in its own window.
openFrame : function () {
openNewWindowWith(this.target.ownerDocument.location.href, null, true);
openNewWindowWith(this.target.ownerDocument.location.href, null, null);
},
// Open clicked-in frame in the same window
// Open clicked-in frame in the same window.
showOnlyThisFrame : function () {
window.loadURI(this.target.ownerDocument.location.href, null, null);
},
@@ -4452,19 +4419,45 @@ nsContextMenu.prototype = {
},
// Change current window to the URL of the image.
viewImage : function (e) {
urlSecurityCheck( this.imageURL, document )
urlSecurityCheck( this.imageURL, this.docURL );
openUILink( this.imageURL, e );
},
// Change current window to the URL of the background image.
viewBGImage : function (e) {
urlSecurityCheck( this.bgImageURL, document )
urlSecurityCheck( this.bgImageURL, this.docURL );
openUILink( this.bgImageURL, e );
},
disableSetDesktopBackground: function() {
// Disable the Set as Desktop Background menu item if we're still trying
// to load the image or the load failed.
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
if (("complete" in this.target) && !this.target.complete)
return true;
else if (makeURI(this.target.src).scheme == "javascript")
return true;
else if (this.target instanceof nsIImageLoadingContent) {
var request = this.target.QueryInterface(nsIImageLoadingContent)
.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
if (!request)
return true;
}
return false;
},
setDesktopBackground: function() {
// Paranoia: check disableSetDesktopBackground again, in case the
// image changed since the context menu was initiated.
if (this.disableSetDesktopBackground())
return;
urlSecurityCheck(this.target.src, this.docURL);
// Confirm since it's annoying if you hit this accidentally.
const kDesktopBackgroundURL =
"chrome://browser/content/setDesktopBackground.xul";
#ifdef XP_MACOSX
// On Mac, the Set Desktop Background window is not modal.
// Don't open more than one Set Desktop Background window.
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
@@ -4478,6 +4471,7 @@ nsContextMenu.prototype = {
this.target);
}
#else
// On non-Mac platforms, the Set Wallpaper dialog is modal.
openDialog(kDesktopBackgroundURL, "",
"centerscreen,chrome,dialog,modal,dependent",
this.target);
@@ -4489,15 +4483,17 @@ nsContextMenu.prototype = {
},
// Save URL of clicked-on link.
saveLink : function () {
saveURL( this.linkURL(), this.linkText(), null, true, false );
urlSecurityCheck(this.imageURL, this.docURL);
saveURL( this.linkURL, this.linkText(), null, true, false, makeURI(this.docURL) );
},
sendLink : function () {
MailIntegration.sendMessage( this.linkURL(), "" ); // we don't know the title of the link so pass in an empty string
MailIntegration.sendMessage( this.linkURL, "" ); // we don't know the title of the link so pass in an empty string
},
// Save URL of clicked-on image.
saveImage : function () {
urlSecurityCheck(this.imageURL, this.docURL);
saveImageURL( this.imageURL, null, "SaveImageTitle", false,
false, getReferrer(document) );
false, makeURI(this.docURL) );
},
sendImage : function () {
MailIntegration.sendMessage(this.imageURL, "");
@@ -4528,7 +4524,7 @@ nsContextMenu.prototype = {
// Copy the comma-separated list of email addresses only.
// There are other ways of embedding email addresses in a mailto:
// link, but such complex parsing is beyond us.
var url = this.linkURL();
var url = this.linkURL;
var qmark = url.indexOf( "?" );
var addresses;
@@ -4638,20 +4634,42 @@ nsContextMenu.prototype = {
// Voila!
return node;
},
// Generate fully-qualified URL for clicked-on link.
linkURL : function () {
var wrapper = this.link;
if (wrapper.href) {
return wrapper.href;
// Generate fully qualified URL for clicked-on link.
getLinkURL : function () {
var href = this.link.href;
if (href) {
return href;
}
var href = wrapper.getAttributeNS("http://www.w3.org/1999/xlink",
var href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
"href");
if (!href || !href.match(/\S/)) {
throw "Empty href"; // Without this we try to save as the current doc, for example, HTML case also throws if empty
}
href = makeURLAbsolute(wrapper.baseURI, href);
href = makeURLAbsolute(this.link.baseURI, href);
return href;
},
getLinkURI : function () {
var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
try {
return ioService.newURI(this.linkURL, null, null);
} catch (ex) {
// e.g. empty URL string
return null;
}
},
getLinkProtocol : function () {
if (this.linkURI) {
return this.linkURI.scheme; // can be |undefined|
} else {
return null;
}
},
// Get text of link.
linkText : function () {
var text = gatherTextUnder( this.link );
@@ -4660,17 +4678,7 @@ nsContextMenu.prototype = {
if (!text || !text.match(/\S/)) {
text = this.link.getAttribute("alt");
if (!text || !text.match(/\S/)) {
var wrapper = this.link;
if (wrapper.href) {
text = wrapper.href;
} else {
text = wrapper.getAttributeNS("http://www.w3.org/1999/xlink",
"href");
if (text && text.match(/\S/)) {
text = makeURLAbsolute(wrapper.baseURI, text);
}
}
text = this.linkURL;
}
}
}
@@ -4678,8 +4686,8 @@ nsContextMenu.prototype = {
return text;
},
//Get selected object and convert it to a string to get
//selected text. Only use the first 15 chars.
// Get selected object and convert it to a string to get
// selected text. Only display the first 15 chars.
isTextSelection : function() {
var result = false;
var selection = this.searchSelected(16);
@@ -4968,6 +4976,8 @@ function asyncOpenWebPanel(event)
function handleLinkClick(event, href, linkNode)
{
var docURL = event.target.ownerDocument.location.href;
switch (event.button) {
case 0:
#ifdef XP_MACOSX
@@ -4975,19 +4985,19 @@ function handleLinkClick(event, href, linkNode)
#else
if (event.ctrlKey) {
#endif
openNewTabWith(href, linkNode, event, true);
openNewTabWith(href, docURL, null, event);
event.preventBubble();
return true;
}
// if left button clicked
if (event.shiftKey) {
openNewWindowWith(href, linkNode, true);
openNewWindowWith(href, docURL, null);
event.preventBubble();
return true;
}
if (event.altKey) {
saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true, true);
saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true, true, makeURI(docURL));
return true;
}
@@ -5001,9 +5011,9 @@ function handleLinkClick(event, href, linkNode)
tab = true;
}
if (tab)
openNewTabWith(href, linkNode, event, true);
openNewTabWith(href, docURL, null, event);
else
openNewWindowWith(href, linkNode, true);
openNewWindowWith(href, docURL, null);
event.preventBubble();
return true;
}

View File

@@ -38,16 +38,22 @@
#
# ***** END LICENSE BLOCK *****
/**
* Determine whether or not a given focused DOMWindow is in the content
* area.
**/
// linkNode is not used anymore
function openNewTabWith(href, linkNode, event, securityCheck, postData, sendReferrer)
/**
* openNewTabWith: opens a new tab with the given URL.
*
* @param href The URL to open (as a string).
* @param sourceURL The URL of the document from which the URL came, or null.
* This is used to set the referrer header and to do a security check of whether
* the document as allowed to reference the URL.
* If null, there will be no referrer header and no security check.
* @param postData Form POST data, or null.
* @param event The triggering event (for the purpose of determining whether to open in the background), or null
*/
function openNewTabWith(href, sourceURL, postData, event)
{
if (securityCheck)
urlSecurityCheck(href, document);
if (sourceURL)
urlSecurityCheck(href, sourceURL);
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
@@ -74,17 +80,15 @@ function openNewTabWith(href, linkNode, event, securityCheck, postData, sendRefe
// open link in new tab
var browser = top.document.getElementById("content");
// If sendReferrer is not specified, default to true
var referrer = (sendReferrer == false) ? null : getReferrer(document);
var referrerURI = sourceURL ? makeURI(sourceURL) : null;
browser.loadOneTab(href, referrer, originCharset, postData, loadInBackground);
browser.loadOneTab(href, referrerURI, originCharset, postData, loadInBackground);
}
// linkNode is not used anymore
function openNewWindowWith(href, linkNode, securityCheck, postData, sendReferrer)
function openNewWindowWith(href, sourceURL, postData)
{
if (securityCheck)
urlSecurityCheck(href, document);
if (sourceURL)
urlSecurityCheck(href, sourceURL);
// if and only if the current window is a browser window and it has a document with a character
// set, then extract the current charset menu setting from the current document and use it to
@@ -94,18 +98,20 @@ function openNewWindowWith(href, linkNode, securityCheck, postData, sendReferrer
if (wintype == "navigator:browser")
charsetArg = "charset=" + window.content.document.characterSet;
// If sendReferrer is not specified, default to true
var referrer = (sendReferrer == false) ? null : getReferrer(document);
var referrerURI = sourceURL ? makeURI(sourceURL) : null;
window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", href, charsetArg, referrer, postData);
window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", href, charsetArg, referrerURI, postData);
}
function urlSecurityCheck(url, doc)
/**
* urlSecurityCheck: JavaScript wrapper for CheckLoadURI.
* If |sourceURL| is not allowed to link to |url|, this function throws with an error message.
*
* @param url The URL a page has linked to.
* @param sourceURL The URL of the document from which the URL came.
*/
function urlSecurityCheck(url, sourceURL)
{
// URL Loading Security Check
var focusedWindow = doc.commandDispatcher.focusedWindow;
var sourceURL = getContentFrameURI(focusedWindow);
var sourceURI = makeURI(sourceURL);
var destURI = makeURI(url);
@@ -115,7 +121,7 @@ function urlSecurityCheck(url, doc)
try {
secMan.checkLoadURI(sourceURI, destURI, nsIScriptSecurityManager.STANDARD);
} catch (e) {
throw "Load of " + url + " denied.";
throw "Load of " + url + " from " + sourceURL + " denied.";
}
}
@@ -142,28 +148,6 @@ function isContentFrame(aFocusedWindow)
return (aFocusedWindow.top == window.content);
}
function getContentFrameURI(aFocusedWindow)
{
var contentFrame = isContentFrame(aFocusedWindow) ? aFocusedWindow : window.content;
if (contentFrame)
return contentFrame.location.href;
else
return null;
}
function getReferrer(doc)
{
var focusedWindow = doc.commandDispatcher.focusedWindow;
var sourceURL = getContentFrameURI(focusedWindow);
if (sourceURL) {
try {
return makeURI(sourceURL);
}
catch (e) { }
}
return null;
}
const kSaveAsType_Complete = 0; // Save document with attached objects
// const kSaveAsType_URL = 1; // Save document or URL by itself