Bug 574006 - Support context menu and open-in-new-tab behavior [r=vingtetun]

This commit is contained in:
Mark Finkle 2010-06-29 14:15:07 -04:00
Родитель b7728049f2
Коммит f824b478cb
4 изменённых файлов: 170 добавлений и 143 удалений

Просмотреть файл

@ -114,9 +114,9 @@ let Util = {
let link = null;
while (target) {
if (target instanceof HTMLAnchorElement ||
target instanceof HTMLAreaElement ||
target instanceof HTMLLinkElement) {
if (target instanceof Ci.nsIDOMHTMLAnchorElement ||
target instanceof Ci.nsIDOMHTMLAreaElement ||
target instanceof Ci.nsIDOMHTMLLinkElement) {
if (target.hasAttribute("href"))
link = target;
}
@ -129,9 +129,13 @@ let Util = {
return null;
},
makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
return gIOService.newURI(aURL, aOriginCharset, aBaseURI);
},
makeURLAbsolute: function makeURLAbsolute(base, url) {
// Note: makeURI() will throw if url is not a valid URI
return makeURI(url, null, makeURI(base)).spec;
return this.makeURI(url, null, this.makeURI(base)).spec;
},
clamp: function(num, min, max) {

Просмотреть файл

@ -388,9 +388,10 @@ var BrowserUI = {
messageManager.addMessageListener("DOMWillOpenModalDialog", this);
messageManager.addMessageListener("DOMWindowClose", this);
// listen returns messages from content
messageManager.addMessageListener("Browser:SaveAs:Return", this);
messageManager.addMessageListener("Browser:Highlight", this);
messageManager.addMessageListener("Browser:OpenURI", this);
messageManager.addMessageListener("Browser:ContextMenu", ContextHelper);
messageManager.addMessageListener("Browser:SaveAs:Return", this);
// listening mousedown for automatically dismiss some popups (e.g. larry)
window.addEventListener("mousedown", this, true);
@ -818,6 +819,9 @@ var BrowserUI = {
}
TapHighlightHelper.show(rects);
break;
case "Browser:OpenURI":
Browser.addTab(json.uri, false, Browser.selectedTab);
}
return {};
@ -2006,135 +2010,40 @@ var SelectHelper = {
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
var ContextHelper = {
popupNode: null,
onLink: false,
onSaveableLink: false,
onVoiceLink: false,
onImage: false,
onLoadedImage: false,
linkURL: "",
linkProtocol: null,
mediaURL: "",
popupState: null,
_clearState: function ch_clearState() {
this.popupNode = null;
this.onLink = false;
this.onSaveableLink = false;
this.onVoiceLink = false;
this.onImage = false;
this.onLoadedImage = false;
this.linkURL = "";
this.linkProtocol = null;
this.mediaURL = "";
},
receiveMessage: function ch_receiveMessage(aMessage) {
this.popupState = aMessage.json;
this.popupState.browser = aMessage.target;
_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);
},
_getURI: function ch_getURI(aURL) {
try {
return makeURI(aURL);
} catch (ex) { }
return null;
},
_getProtocol: function ch_getProtocol(aURI) {
if (aURI)
return aURI.scheme;
return null;
},
_isSaveable: function ch_isSaveable(aProtocol) {
// We don't do the Right Thing for news/snews yet, so turn them off until we do
return aProtocol && !(aProtocol == "mailto" || aProtocol == "javascript" || aProtocol == "news" || aProtocol == "snews");
},
_isVoice: function ch_isVoice(aProtocol) {
// Collection of protocols related to voice or data links
return aProtocol && (aProtocol == "tel" || aProtocol == "callto" || aProtocol == "sip" || aProtocol == "voipto");
},
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 request = this.popupNode.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
this.onLoadedImage = true;
}
}
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.linkURL = this._getLinkURL(elem);
this.linkProtocol = this._getProtocol(this._getURI(this.linkURL));
this.onLink = true;
this.onSaveableLink = this._isSaveable(this.linkProtocol);
this.onVoiceLink = this._isVoice(this.linkProtocol);
}
}
elem = elem.parentNode;
}
let first = last = null;
let first = null;
let last = null;
let commands = document.getElementById("context-commands");
for (let i=0; i<commands.childElementCount; i++) {
let command = commands.children[i];
let types = command.getAttribute("type").split(/\s+/);
command.removeAttribute("selector");
if (types.indexOf("image") != -1 && this.onImage) {
if (types.indexOf("image") != -1 && this.popupState.onImage) {
first = (first ? first : command);
last = command;
command.hidden = false;
continue;
} else if (types.indexOf("image-loaded") != -1 && this.onLoadedImage) {
} else if (types.indexOf("image-loaded") != -1 && this.popupState.onLoadedImage) {
first = (first ? first : command);
last = command;
command.hidden = false;
continue;
} else if (types.indexOf("link") != -1 && this.onSaveableLink) {
} else if (types.indexOf("link") != -1 && this.popupState.onSaveableLink) {
first = (first ? first : command);
last = command;
command.hidden = false;
continue;
} else if (types.indexOf("callto") != -1 && this.onVoiceLink) {
} else if (types.indexOf("callto") != -1 && this.popupState.onVoiceLink) {
first = (first ? first : command);
last = command;
command.hidden = false;
continue;
} else if (types.indexOf("mailto") != -1 && this.onLink && this.linkProtocol == "mailto") {
} else if (types.indexOf("mailto") != -1 && this.popupState.onLink && this.popupState.linkProtocol == "mailto") {
first = (first ? first : command);
last = command;
command.hidden = false;
@ -2144,7 +2053,7 @@ var ContextHelper = {
}
if (!first) {
this._clearState();
this.popupState = null;
return;
}
@ -2152,10 +2061,10 @@ var ContextHelper = {
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;
if (this.popupState.onImage)
label.value = this.popupState.mediaURL;
if (this.popupState.onLink)
label.value = this.popupState.linkURL;
let container = document.getElementById("context-popup");
container.hidden = false;
@ -2179,7 +2088,7 @@ var ContextHelper = {
},
hide: function ch_hide() {
this._clearState();
this.popupState = null;
let container = document.getElementById("context-popup");
container.hidden = true;
@ -2190,12 +2099,12 @@ var ContextHelper = {
var ContextCommands = {
openInNewTab: function cc_openInNewTab(aEvent) {
Browser.addTab(ContextHelper.linkURL, false, Browser.selectedTab);
Browser.addTab(ContextHelper.popupState.linkURL, false, Browser.selectedTab);
},
saveImage: function cc_saveImage(aEvent) {
let doc = ContextHelper.popupNode.ownerDocument;
saveImageURL(ContextHelper.mediaURL, null, "SaveImageTitle", false, false, doc.documentURIObject);
let browser = ContextHelper.popupState.browser;
saveImageURL(ContextHelper.popupState.mediaURL, null, "SaveImageTitle", false, false, browser.documentURI);
}
}

Просмотреть файл

@ -476,9 +476,6 @@ 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);
@ -1555,10 +1552,11 @@ function ContentCustomClicker(browserView) {
}
ContentCustomClicker.prototype = {
_dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY) {
_dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY, aModifiers) {
let aModifiers = aModifiers || null;
let browser = this._browserView.getBrowser();
let [x, y] = Browser.transformClientToBrowser(aX, aY);
browser.messageManager.sendAsyncMessage(aName, { x: x, y: y });
browser.messageManager.sendAsyncMessage(aName, { x: x, y: y, modifiers: aModifiers });
},
mouseDown: function mouseDown(aX, aY) {
@ -1576,24 +1574,24 @@ ContentCustomClicker.prototype = {
panBegin: function panBegin() {
TapHighlightHelper.hide();
let browser = this._browserView.getBrowser();
browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
},
singleClick: function singleClick(aX, aY, aModifiers) {
TapHighlightHelper.hide();
this._dispatchMouseEvent("Browser:MouseUp", aX, aY);
// TODO e10s: handle modifiers for clicks
//
// if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
// let uri = Util.getHrefForElement(element);
// if (uri)
// Browser.addTab(uri, false);
// }
// Cancel the mouse click if we are showing a context menu
if (ContextHelper.popupState)
this._dispatchMouseEvent("Browser:MouseCancel", {});
else
this._dispatchMouseEvent("Browser:MouseUp", aX, aY, aModifiers);
},
doubleClick: function doubleClick(aX1, aY1, aX2, aY2) {
TapHighlightHelper.hide();
let browser = this._browserView.getBrowser();
browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});

Просмотреть файл

@ -12,6 +12,8 @@ let gPrefService = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch2);
let gObserverService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
let gIOService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
let XULDocument = Ci.nsIDOMXULDocument;
let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
@ -443,6 +445,7 @@ Content.prototype = {
let json = aMessage.json;
let x = json.x;
let y = json.y;
let modifiers = json.modifiers;
switch (aMessage.name) {
case "Browser:Blur":
@ -459,20 +462,31 @@ Content.prototype = {
if (this._overlayTimeout)
return;
this._overlayTimeout = content.setTimeout(function() {
let element = elementFromPoint(x, y);
if (!element || !element.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label"))
return;
let element = elementFromPoint(x, y);
if (!element)
return;
let rects = getContentClientRects(element);
sendAsyncMessage("Browser:Highlight", { rects: rects });
}, kTapOverlayTimeout);
this._sendMouseEvent("mousedown", element, x, y);
// If we don't release the implicit capture, we'll get dragging problems
// when a contextmenu is displayed
element.ownerDocument.releaseCapture();
if (element.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label")) {
this._overlayTimeout = content.setTimeout(function() {
let rects = getContentClientRects(element);
sendSyncMessage("Browser:Highlight", { rects: rects });
}, kTapOverlayTimeout);
}
break;
case "Browser:MouseUp": {
let element = elementFromPoint(x, y);
if (!this._formAssistant.open(element)) {
this._sendMouseEvent("mousedown", element, x, y);
if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
let uri = Util.getHrefForElement(element);
if (uri)
sendAsyncMessage("Browser:OpenURI", { uri: uri });
} else if (!this._formAssistant.open(element)) {
this._sendMouseEvent("mouseup", element, x, y);
}
}
@ -690,6 +704,108 @@ let ViewportHandler = {
ViewportHandler.init();
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
var ContextHandler = {
_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);
},
_getURI: function ch_getURI(aURL) {
try {
return Util.makeURI(aURL);
} catch (ex) { }
return null;
},
_getProtocol: function ch_getProtocol(aURI) {
if (aURI)
return aURI.scheme;
return null;
},
_isSaveable: function ch_isSaveable(aProtocol) {
// We don't do the Right Thing for news/snews yet, so turn them off until we do
return aProtocol && !(aProtocol == "mailto" || aProtocol == "javascript" || aProtocol == "news" || aProtocol == "snews");
},
_isVoice: function ch_isVoice(aProtocol) {
// Collection of protocols related to voice or data links
return aProtocol && (aProtocol == "tel" || aProtocol == "callto" || aProtocol == "sip" || aProtocol == "voipto");
},
init: function ch_init() {
addEventListener("contextmenu", this, false);
},
handleEvent: function ch_handleEvent(aEvent) {
let state = {
onLink: false,
onSaveableLink: false,
onVoiceLink: false,
onImage: false,
onLoadedImage: false,
linkURL: "",
linkProtocol: null,
mediaURL: ""
};
let popupNode = elementFromPoint(aEvent.clientX, aEvent.clientY);
// Do checks for nodes that never have children.
if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
// See if the user clicked on an image.
if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
state.onImage = true;
state.mediaURL = popupNode.currentURI.spec;
let request = popupNode.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
state.onLoadedImage = true;
}
}
let elem = popupNode;
while (elem) {
if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
// Link?
if (!this.onLink &&
((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
(elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href) ||
elem instanceof Ci.nsIDOMHTMLLinkElement ||
elem.getAttributeNS(kXLinkNamespace, "type") == "simple")) {
// Target is a link or a descendant of a link.
state.linkURL = this._getLinkURL(elem);
state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
state.onLink = true;
state.onSaveableLink = this._isSaveable(state.linkProtocol);
state.onVoiceLink = this._isVoice(state.linkProtocol);
}
}
elem = elem.parentNode;
}
sendAsyncMessage("Browser:ContextMenu", state);
}
};
ContextHandler.init();
var FormSubmitObserver = {
init: function init() {
gObserverService.addObserver(this, "formsubmit", false);