зеркало из https://github.com/mozilla/gecko-dev.git
Bug 574006 - Support context menu and open-in-new-tab behavior [r=vingtetun]
This commit is contained in:
Родитель
b7728049f2
Коммит
f824b478cb
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче