Bug 551711 - Open Link in New Tab via content panel [r=vingtetun]

This commit is contained in:
Mark Finkle 2010-03-24 14:22:18 -04:00
parent 1eda5208c8
commit 7efca738ba
8 changed files with 232 additions and 0 deletions

View File

@ -139,6 +139,7 @@ function InputHandler(browserViewContainer) {
this.listenFor(browserViewContainer, "keyup");
this.listenFor(browserViewContainer, "DOMMouseScroll");
this.listenFor(browserViewContainer, "MozMousePixelScroll");
this.listenFor(browserViewContainer, "contextmenu");
this.addModule(new MouseModule(this, browserViewContainer));
this.addModule(new ScrollwheelModule(this, browserViewContainer));
@ -418,6 +419,11 @@ function MouseModule(owner, browserViewContainer) {
MouseModule.prototype = {
handleEvent: function handleEvent(evInfo) {
// TODO: Make "contextmenu" a first class part of InputHandler
// Bug 554639
if (evInfo.event.type == "contextmenu")
this._cleanClickBuffer();
if (evInfo.event.button !== 0) // avoid all but a clean left click
return;

View File

@ -108,6 +108,11 @@ let Util = {
return null;
},
makeURLAbsolute: function makeURLAbsolute(base, url) {
// Note: makeURI() will throw if url is not a valid URI
return makeURI(url, null, makeURI(base)).spec;
},
contentIsHandheld: function contentIsHandheld(browser) {
let doctype = browser.contentDocument.doctype;
if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId))

View File

@ -1699,6 +1699,142 @@ var SelectHelper = {
}
};
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
var ContextHelper = {
popupNode: null,
onLink: false,
onImage: false,
linkURL: "",
mediaURL: "",
_clearState: function ch_clearState() {
this.popupNode = null;
this.onLink = false;
this.onImage = false;
this.linkURL = "";
this.mediaURL = "";
},
_getLinkURL: function ch_getLinkURL(aLink) {
let href = aLink.href;
if (href)
return href;
href = aLink.getAttributeNS(kXLinkNamespace, "href");
if (!href || !href.match(/\S/)) {
// Without this we try to save as the current doc,
// for example, HTML case also throws if empty
throw "Empty href";
}
return Util.makeURLAbsolute(aLink.baseURI, href);
},
handleEvent: function ch_handleEvent(aEvent) {
this._clearState();
let [elementX, elementY] = Browser.transformClientToBrowser(aEvent.clientX, aEvent.clientY);
this.popupNode = Browser.elementFromPoint(elementX, elementY);
// Do checks for nodes that never have children.
if (this.popupNode.nodeType == Node.ELEMENT_NODE) {
// See if the user clicked on an image.
if (this.popupNode instanceof Ci.nsIImageLoadingContent && this.popupNode.currentURI) {
this.onImage = true;
this.mediaURL = this.popupNode.currentURI.spec;
}
}
let elem = this.popupNode;
while (elem) {
if (elem.nodeType == Node.ELEMENT_NODE) {
// Link?
if (!this.onLink &&
((elem instanceof HTMLAnchorElement && elem.href) ||
(elem instanceof HTMLAreaElement && elem.href) ||
elem instanceof HTMLLinkElement ||
elem.getAttributeNS(kXLinkNamespace, "type") == "simple")) {
// Target is a link or a descendant of a link.
this.onLink = true;
this.linkURL = this._getLinkURL(elem);
}
}
elem = elem.parentNode;
}
let first = last = null;
let commands = document.getElementById("context-commands");
for (let i=0; i<commands.childElementCount; i++) {
let command = commands.children[i];
let type = command.getAttribute("type");
command.removeAttribute("selector");
if (type.indexOf("image") != -1 && this.onImage) {
first = (first ? first : command);
last = command;
command.hidden = false;
continue;
} else if (type.indexOf("link") != -1 && this.onLink) {
first = (first ? first : command);
last = command;
command.hidden = false;
continue;
}
command.hidden = true;
}
if (!first) {
this._clearState();
return;
}
first.setAttribute("selector", "first-child");
last.setAttribute("selector", "last-child");
let label = document.getElementById("context-hint");
if (this.onImage)
label.value = this.mediaURL;
if (this.onLink)
label.value = this.linkURL;
let container = document.getElementById("context-popup");
container.hidden = false;
let rect = container.getBoundingClientRect();
let height = Math.min(rect.height, 0.75 * window.innerWidth);
let width = Math.min(rect.width, 0.75 * window.innerWidth);
container.height = height;
container.width = width;
container.top = (window.innerHeight - height) / 2;
container.left = (window.innerWidth - width) / 2;
BrowserUI.pushPopup(this, [container]);
},
hide: function ch_hide() {
this._clearState();
let container = document.getElementById("context-popup");
container.hidden = true;
BrowserUI.popPopup();
}
};
var ContextCommands = {
openInNewTab: function cc_openInNewTab(aEvent) {
Browser.addTab(ContextHelper.linkURL, false);
},
saveImage: function cc_saveImage(aEvent) {
let doc = ContextHelper.popupNode.ownerDocument;
saveImageURL(ContextHelper.mediaURL, null, "SaveImageTitle", false, false, doc.documentURIObject);
}
}
function removeBookmarksForURI(aURI) {
//XXX blargle xpconnect! might not matter, but a method on
// nsINavBookmarksService that takes an array of items to

View File

@ -438,6 +438,9 @@ var Browser = {
notifications.addEventListener("AlertActive", notificationHandler, false);
notifications.addEventListener("AlertClose", notificationHandler, false);
// Add context helper to the content area only
container.addEventListener("contextmenu", ContextHelper, false);
// initialize input handling
ih = new InputHandler(container);

View File

@ -69,6 +69,7 @@
xmlns:html="http://www.w3.org/1999/xhtml">
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/javascript" src="chrome://browser/content/commandUtil.js"/>
<script type="application/javascript" src="chrome://browser/content/exceptions.js"/>
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
@ -481,6 +482,20 @@
</hbox>
</vbox>
<vbox id="context-popup" hidden="true" class="dialog-dark" top="0" left="0">
<hbox id="context-header">
<label id="context-hint" crop="center" flex="1"/>
</hbox>
<richlistbox id="context-commands" onclick="ContextHelper.hide();">
<richlistitem id="context-openinnewtab" type="link" onclick="ContextCommands.openInNewTab(event);">
<label value="&contextOpenInNewTab.label;"/>
</richlistitem>
<richlistitem id="context-saveimage" type="image" onclick="ContextCommands.saveImage(event);">
<label value="&contextSaveImage.label;"/>
</richlistitem>
</richlistbox>
</vbox>
<!-- alerts for content -->
<hbox id="alerts-container" hidden="true" align="start" class="dialog-dark" top="0" left="0"
onclick="AlertsHelper.click(event);">

View File

@ -91,3 +91,6 @@
<!ENTITY consoleErrFile.label "Source File:">
<!ENTITY consoleErrLine.label "Line:">
<!ENTITY consoleErrColumn.label "Column:">
<!ENTITY contextOpenInNewTab.label "Open Link in New Tab">
<!ENTITY contextSaveImage.label "Save Image">

View File

@ -1041,6 +1041,38 @@ box[type="documenttab"]:only-child .documenttab-close {
list-style-image: url("chrome://browser/skin/images/check-30.png");
}
/* context popup ----------------------------------------------------------- */
#context-popup {
/* Remove some dialog-dark styles */
padding: 8px 0 0 0;
border: none;
}
#context-header > label {
font-size: 18px;
padding: 4px;
}
#context-commands {
border: 1px solid rgb(207,207,207);
-moz-border-radius: 0 0 8px 8px;
}
#context-commands > richlistitem {
-moz-box-align: center;
background-color: rgb(245,245,245);
min-width: 200px; /* keep the command from being too narrow */
}
#context-commands > richlistitem[selector="first-child"] {
background: -moz-linear-gradient(top, rgb(255,255,255), rgb(245,245,245));
}
#context-commands > richlistitem[selector="last-child"] {
background: -moz-linear-gradient(top, rgb(245,245,245), rgb(215,215,215));
-moz-border-radius: 0 0 8px 8px !important;
}
.modal-block {
-moz-box-align: center;
-moz-box-pack: center;

View File

@ -708,6 +708,38 @@ box[type="documenttab"]:only-child .documenttab-close {
min-width: 30px;
}
/* context popup ----------------------------------------------------------- */
#context-popup {
/* Remove some dialog-dark styles */
padding: 0.5mm 0 0 0;
border: none;
}
#context-header > label {
font-size: 8pt;
padding: 0.25mm;
}
#context-commands {
border: 1px solid rgb(207,207,207);
-moz-border-radius: 0 0 0.5mm 0.5mm;
}
#context-commands > richlistitem {
-moz-box-align: center;
background-color: rgb(245,245,245);
min-width: 200px; /* keep the command from being too narrow */
}
#context-commands > richlistitem[selector="first-child"] {
background: -moz-linear-gradient(top, rgb(255,255,255), rgb(245,245,245));
}
#context-commands > richlistitem[selector="last-child"] {
background: -moz-linear-gradient(top, rgb(245,245,245), rgb(215,215,215));
-moz-border-radius: 0 0 8px 8px !important;
}
.modal-block {
-moz-box-align: center;
-moz-box-pack: center;