Bug 1134769, move selection info retrieval on context menu to content process, r=mconley

This commit is contained in:
Neil Deakin 2015-04-29 08:38:42 -04:00
parent 531d28100a
commit e662ba16db
8 changed files with 131 additions and 166 deletions

View File

@ -5278,51 +5278,17 @@ function UpdateDynamicShortcutTooltipText(aTooltip) {
aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId));
}
/**
* Gets the selected text in the active browser. Leading and trailing
* whitespace is removed, and consecutive whitespace is replaced by a single
* space. A maximum of 150 characters will be returned, regardless of the value
* of aCharLen.
*
* @param aCharLen
* The maximum number of characters to return.
*/
function getBrowserSelection(aCharLen) {
// selections of more than 150 characters aren't useful
const kMaxSelectionLen = 150;
const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
Deprecated.warning("getBrowserSelection",
"https://bugzilla.mozilla.org/show_bug.cgi?id=1134769");
let [element, focusedWindow] = BrowserUtils.getFocusSync(document);
var selection = focusedWindow.getSelection().toString();
// try getting a selected text in text input.
if (!selection) {
var isOnTextInput = function isOnTextInput(elem) {
// we avoid to return a value if a selection is in password field.
// ref. bug 565717
return elem instanceof HTMLTextAreaElement ||
(elem instanceof HTMLInputElement && elem.mozIsTextField(true));
};
if (isOnTextInput(element)) {
selection = element.QueryInterface(Ci.nsIDOMNSEditableElement)
.editor.selection.toString();
}
let focusedElement = document.activeElement;
if (focusedElement && focusedElement.localName == "browser" &&
focusedElement.isRemoteBrowser) {
throw "getBrowserSelection doesn't support child process windows";
}
if (selection) {
if (selection.length > charLen) {
// only use the first charLen important chars. see bug 221361
var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
pattern.test(selection);
selection = RegExp.lastMatch;
}
selection = selection.trim().replace(/\s+/g, " ");
if (selection.length > charLen)
selection = selection.substr(0, charLen);
}
return selection;
return BrowserUtils.getSelectionDetails(window, aCharLen).text;
}
var gWebPanelURI;

View File

@ -114,6 +114,8 @@ let handleContentContextMenu = function (event) {
}
}
let selectionInfo = BrowserUtils.getSelectionDetails(content);
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
let editFlags = SpellCheckHelper.isEditable(event.target, content);
let spellInfo;
@ -136,7 +138,7 @@ let handleContentContextMenu = function (event) {
{ editFlags, spellInfo, customMenuItems, addonInfo,
principal, docLocation, charSet, baseURI, referrer,
referrerPolicy, contentType, contentDisposition,
frameOuterWindowID },
frameOuterWindowID, selectionInfo },
{ event, popupNode: event.target });
}
else {
@ -156,6 +158,7 @@ let handleContentContextMenu = function (event) {
referrerPolicy: referrerPolicy,
contentType: contentType,
contentDisposition: contentDisposition,
selectionInfo: selectionInfo,
};
}
}

View File

@ -42,7 +42,7 @@ nsContextMenu.prototype = {
Ci.nsIPrefLocalizedString).data;
} catch (e) { }
this.isContentSelected = this.isContentSelection();
this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
this.onPlainTextLink = false;
// Initialize (disable/remove) menu items.
@ -93,67 +93,15 @@ nsContextMenu.prototype = {
(mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
}
// Time to do some bad things and see if we've highlighted a URL that
// isn't actually linked.
if (this.isTextSelected && !this.onLink) {
// Ok, we have some text, let's figure out if it looks like a URL.
let selection = this.focusedWindow.getSelection();
let linkText = selection.toString().trim();
let uri;
if (/^(?:https?|ftp):/i.test(linkText)) {
try {
uri = makeURI(linkText);
} catch (ex) {}
}
// Check if this could be a valid url, just missing the protocol.
else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
// Now let's see if this is an intentional link selection. Our guess is
// based on whether the selection begins/ends with whitespace or is
// preceded/followed by a non-word character.
if (this.isTextSelected && !this.onLink &&
this.selectionInfo && this.selectionInfo.linkURL) {
this.linkURL = this.selectionInfo.linkURL;
try {
this.linkURI = makeURI(this.linkURL);
} catch (ex) {}
// selection.toString() trims trailing whitespace, so we look for
// that explicitly in the first and last ranges.
let beginRange = selection.getRangeAt(0);
let delimitedAtStart = /^\s/.test(beginRange);
if (!delimitedAtStart) {
let container = beginRange.startContainer;
let offset = beginRange.startOffset;
if (container.nodeType == Node.TEXT_NODE && offset > 0)
delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
else
delimitedAtStart = true;
}
let delimitedAtEnd = false;
if (delimitedAtStart) {
let endRange = selection.getRangeAt(selection.rangeCount - 1);
delimitedAtEnd = /\s$/.test(endRange);
if (!delimitedAtEnd) {
let container = endRange.endContainer;
let offset = endRange.endOffset;
if (container.nodeType == Node.TEXT_NODE &&
offset < container.textContent.length)
delimitedAtEnd = /\W/.test(container.textContent[offset]);
else
delimitedAtEnd = true;
}
}
if (delimitedAtStart && delimitedAtEnd) {
let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
.getService(Ci.nsIURIFixup);
try {
uri = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
} catch (ex) {}
}
}
if (uri && uri.host) {
this.linkURI = uri;
this.linkURL = this.linkURI.spec;
this.linkText = linkText;
this.onPlainTextLink = true;
}
this.linkText = this.selectionInfo.linkText;
this.onPlainTextLink = true;
}
var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
@ -590,16 +538,19 @@ nsContextMenu.prototype = {
this.isDesignMode = false;
this.onCTPPlugin = false;
this.canSpellCheck = false;
this.textSelected = getBrowserSelection();
if (this.isRemote) {
this.selectionInfo = gContextMenuContentData.selectionInfo;
} else {
this.selectionInfo = BrowserUtils.getSelectionDetails(window);
}
this.textSelected = this.selectionInfo.text;
this.isTextSelected = this.textSelected.length != 0;
// Remember the node that was clicked.
this.target = aNode;
let [elt, win] = BrowserUtils.getFocusSync(document);
this.focusedWindow = win;
this.focusedElement = elt;
let ownerDoc = this.target.ownerDocument;
this.ownerDoc = ownerDoc;
@ -1545,11 +1496,6 @@ nsContextMenu.prototype = {
return text;
},
// Returns true if anything is selected.
isContentSelection: function() {
return !this.focusedWindow.getSelection().isCollapsed;
},
isMediaURLReusable: function(aURL) {
return !/^(?:blob|mediasource):/.test(aURL);
},

View File

@ -3763,6 +3763,7 @@
contentType: aMessage.data.contentType,
contentDisposition: aMessage.data.contentDisposition,
frameOuterWindowID: aMessage.data.frameOuterWindowID,
selectionInfo: aMessage.data.selectionInfo,
};
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
let event = gContextMenuContentData.event;

View File

@ -21,22 +21,6 @@ if (AppConstants.MOZ_CRASHREPORTER) {
"nsICrashReporter");
}
let FocusSyncHandler = {
init: function() {
sendAsyncMessage("SetSyncHandler", {}, {handler: this});
},
getFocusedElementAndWindow: function() {
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
let focusedWindow = {};
let elt = fm.getFocusedElementForWindow(content, true, focusedWindow);
return [elt, focusedWindow.value];
},
};
FocusSyncHandler.init();
let WebProgressListener = {
init: function() {
this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]

View File

@ -1112,7 +1112,6 @@
"_contentTitle",
"_characterSet",
"_contentPrincipal",
"_syncHandler",
"_imageDocument",
"_fullZoom",
"_textZoom",

View File

@ -163,12 +163,6 @@
onget="return this.contentWindowAsCPOW ? this.contentWindowAsCPOW.document : null"
readonly="true"/>
<field name="_syncHandler">null</field>
<property name="syncHandler"
onget="return this._syncHandler"
readonly="true"/>
<field name="_imageDocument">null</field>
<property name="imageDocument"
@ -244,7 +238,6 @@
this.messageManager.addMessageListener("Browser:Init", this);
this.messageManager.addMessageListener("DOMTitleChanged", this);
this.messageManager.addMessageListener("ImageDocumentLoaded", this);
this.messageManager.addMessageListener("SetSyncHandler", this);
this.messageManager.addMessageListener("DocumentInserted", this);
this.messageManager.addMessageListener("FullZoomChange", this);
this.messageManager.addMessageListener("TextZoomChange", this);
@ -310,10 +303,6 @@
};
break;
case "SetSyncHandler":
this._syncHandler = aMessage.objects.handler;
break;
case "Forms:ShowDropDown": {
Cu.import("resource://gre/modules/SelectParentHelper.jsm");
let menulist = document.getElementById(this.getAttribute("selectmenulist"));

View File

@ -93,31 +93,6 @@ this.BrowserUtils = {
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
@ -318,4 +293,106 @@ this.BrowserUtils = {
}
return true;
},
getSelectionDetails: function(topWindow, aCharLen) {
// selections of more than 150 characters aren't useful
const kMaxSelectionLen = 150;
const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
let focusedWindow = {};
let focusedElement = Services.focus.getFocusedElementForWindow(topWindow, true, focusedWindow);
focusedWindow = focusedWindow.value;
let selection = focusedWindow.getSelection();
let selectionStr = selection.toString();
let collapsed = selection.isCollapsed;
let url;
let linkText;
if (selectionStr) {
// Have some text, let's figure out if it looks like a URL that isn't
// actually a link.
linkText = selectionStr.trim();
if (/^(?:https?|ftp):/i.test(linkText)) {
try {
url = this.makeURI(linkText);
} catch (ex) {}
}
// Check if this could be a valid url, just missing the protocol.
else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
// Now let's see if this is an intentional link selection. Our guess is
// based on whether the selection begins/ends with whitespace or is
// preceded/followed by a non-word character.
// selection.toString() trims trailing whitespace, so we look for
// that explicitly in the first and last ranges.
let beginRange = selection.getRangeAt(0);
let delimitedAtStart = /^\s/.test(beginRange);
if (!delimitedAtStart) {
let container = beginRange.startContainer;
let offset = beginRange.startOffset;
if (container.nodeType == container.TEXT_NODE && offset > 0)
delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
else
delimitedAtStart = true;
}
let delimitedAtEnd = false;
if (delimitedAtStart) {
let endRange = selection.getRangeAt(selection.rangeCount - 1);
delimitedAtEnd = /\s$/.test(endRange);
if (!delimitedAtEnd) {
let container = endRange.endContainer;
let offset = endRange.endOffset;
if (container.nodeType == container.TEXT_NODE &&
offset < container.textContent.length)
delimitedAtEnd = /\W/.test(container.textContent[offset]);
else
delimitedAtEnd = true;
}
}
if (delimitedAtStart && delimitedAtEnd) {
let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
.getService(Ci.nsIURIFixup);
try {
url = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
} catch (ex) {}
}
}
}
// try getting a selected text in text input.
if (!selectionStr && focusedElement instanceof Ci.nsIDOMNSEditableElement) {
// Don't get the selection for password fields. See bug 565717.
if (focusedElement instanceof Ci.nsIDOMHTMLTextAreaElement ||
(focusedElement instanceof Ci.nsIDOMHTMLInputElement &&
focusedElement.mozIsTextField(true))) {
selectionStr = focusedElement.editor.selection.toString();
}
}
if (selectionStr) {
if (selectionStr.length > charLen) {
// only use the first charLen important chars. see bug 221361
var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
pattern.test(selectionStr);
selectionStr = RegExp.lastMatch;
}
selectionStr = selectionStr.trim().replace(/\s+/g, " ");
if (selectionStr.length > charLen) {
selectionStr = selectionStr.substr(0, charLen);
}
}
if (url && !url.host) {
url = null;
}
return { text: selectionStr, docSelectionIsCollapsed: collapsed,
linkURL: url ? url.spec : null, linkText: url ? linkText : "" };
}
};