bug 611741 - Implement copy/paste for text fields in web content r=mfinkle a=2.0+
--- a/mobile/chrome/content/ContextCommands.js
+++ b/mobile/chrome/content/ContextCommands.js
@@ -1,8 +1,9 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
var ContextCommands = {
copy: function cc_copy() {
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
clipboard.copyString(ContextHelper.popupState.string);
let target = ContextHelper.popupState.target;
if (target)
target.focus();
@@ -11,26 +12,39 @@ var ContextCommands = {
#ifdef ANDROID
selectInput: function cc_selectInput() {
let imePicker = Cc["@mozilla.org/imepicker;1"].getService(Ci.nsIIMEPicker);
imePicker.show();
},
#endif
paste: function cc_paste() {
- let data = ContextHelper.popupState.data;
let target = ContextHelper.popupState.target;
- target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
- target.focus();
+ if (target.localName == "browser") {
+ let x = ContextHelper.popupState.x;
+ let y = ContextHelper.popupState.y;
+ let json = {x: x, y: y, command: "paste" };
+ messageManager.sendAsyncMessage("Browser:ContextCommand", json);
+ } else {
+ target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
+ target.focus();
+ }
},
selectAll: function cc_selectAll() {
let target = ContextHelper.popupState.target;
- target.editor.selectAll();
- target.focus();
+ if (target.localName == "browser") {
+ let x = ContextHelper.popupState.x;
+ let y = ContextHelper.popupState.y;
+ let json = {x: x, y: y, command: "select-all" };
+ messageManager.sendAsyncMessage("Browser:ContextCommand", json);
+ } else {
+ target.editor.selectAll();
+ target.focus();
+ }
},
openInNewTab: function cc_openInNewTab() {
Browser.addTab(ContextHelper.popupState.linkURL, false, Browser.selectedTab);
},
saveLink: function cc_saveLink() {
let browser = ContextHelper.popupState.target;
--- a/mobile/chrome/content/common-ui.js
+++ b/mobile/chrome/content/common-ui.js
@@ -1,8 +1,9 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
var BrowserSearch = {
get _popup() {
delete this._popup;
return this._popup = document.getElementById("search-engines-popup");
},
get _list() {
delete this._list;
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -1,8 +1,9 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
// This stays here because otherwise it's hard to tell if there's a parsing error
dump("###################################### content loaded\n");
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
let Cr = Components.results;
@@ -262,16 +263,17 @@ let Content = {
addMessageListener("Browser:MouseLong", this);
addMessageListener("Browser:MouseDown", this);
addMessageListener("Browser:MouseUp", this);
addMessageListener("Browser:MouseCancel", this);
addMessageListener("Browser:SaveAs", this);
addMessageListener("Browser:ZoomToPoint", this);
addMessageListener("Browser:MozApplicationCache:Fetch", this);
addMessageListener("Browser:SetCharset", this);
+ addMessageListener("Browser:ContextCommand", this);
if (Util.isParentProcess())
addEventListener("DOMActivate", this, true);
addEventListener("MozApplicationManifest", this, false);
addEventListener("command", this, false);
addEventListener("pagehide", this, false);
addEventListener("keypress", this, false, false);
@@ -365,16 +367,35 @@ let Content = {
receiveMessage: function receiveMessage(aMessage) {
let json = aMessage.json;
let x = json.x;
let y = json.y;
let modifiers = json.modifiers;
switch (aMessage.name) {
+ case "Browser:ContextCommand": {
+ let wrappedTarget = elementFromPoint(x, y);
+ if (!wrappedTarget)
+ break;
+ let target = wrappedTarget.QueryInterface(Ci.nsIDOMNSEditableElement);
+ if (!target)
+ break;
+ switch (json.command) {
+ case "select-all":
+ target.editor.selectAll();
+ break;
+ case "paste":
+ target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
+ break;
+ }
+ target.focus();
+ break;
+ }
+
case "Browser:Blur":
gFocusManager.clearFocus(content);
break;
case "Browser:MouseOver": {
let element = elementFromPoint(x, y);
if (!element)
return;
@@ -418,16 +439,18 @@ let Content = {
break;
}
#endif
ContextHandler.messageId = json.messageId;
let event = content.document.createEvent("PopupEvents");
event.initEvent("contextmenu", true, true);
+ event.x = x;
+ event.y = y;
element.dispatchEvent(event);
break;
}
case "Browser:MouseUp": {
this._formAssistant.focusSync = true;
let element = elementFromPoint(x, y);
if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
@@ -525,17 +548,17 @@ let Content = {
.getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, currentURI, content);
break;
}
case "Browser:SetCharset": {
let docCharset = docShell.QueryInterface(Ci.nsIDocCharset);
docCharset.charset = json.charset;
-
+
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
break;
}
}
},
_highlightElement: null,
@@ -757,17 +780,19 @@ var ContextHandler = {
return;
let state = {
types: [],
label: "",
linkURL: "",
linkTitle: "",
linkProtocol: null,
- mediaURL: ""
+ mediaURL: "",
+ x: aEvent.x,
+ y: aEvent.y
};
let popupNode = this.popupNode = aEvent.originalTarget;
// 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) {
@@ -791,16 +816,39 @@ var ContextHandler = {
elem.getAttributeNS(kXLinkNamespace, "type") == "simple") {
// Target is a link or a descendant of a link.
state.types.push("link");
state.label = state.linkURL = this._getLinkURL(elem);
state.linkTitle = popupNode.textContent || popupNode.title;
state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
break;
+ } else if (elem instanceof Ci.nsIDOMHTMLInputElement && elem.type === "text") {
+ let selectionStart = elem.selectionStart;
+ let selectionEnd = elem.selectionEnd;
+
+ state.types.push("input-text");
+ if (selectionStart != selectionEnd) {
+ state.types.push("copy");
+ state.string = elem.value.slice(selectionStart, selectionEnd);
+ } else if (elem.value) {
+ state.types.push("copy-all");
+ state.string = elem.value;
+ }
+
+ if (selectionStart > 0 || selectionEnd < elem.textLength)
+ state.types.push("select-all");
+
+ let clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+ let flavors = ["text/unicode"];
+ let hasData = clipboard.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard);
+
+ if (hasData && !elem.readOnly)
+ state.types.push("paste");
+ break;
}
}
elem = elem.parentNode;
}
for (let i = 0; i < this._types.length; i++)
if (this._types[i].handler(state, popupNode))
@@ -871,16 +919,20 @@ ContextHandler.registerType("link-openab
let protocol = aState.linkProtocol;
return (protocol && protocol != "mailto" && protocol != "javascript" && protocol != "news" && protocol != "snews");
});
ContextHandler.registerType("link-shareable", function(aState, aElement) {
return Util.isShareableScheme(aState.linkProtocol);
});
+ContextHandler.registerType("input-text", function(aState, aElement) {
+ return aElement instanceof Ci.nsIDOMHTMLInputElement;
+});
+
["image", "video"].forEach(function(aType) {
ContextHandler.registerType(aType+"-shareable", function(aState, aElement) {
if (aState.types.indexOf(aType) == -1)
return false;
let protocol = ContextHandler._getProtocol(ContextHandler._getURI(aState.mediaURL));
return Util.isShareableScheme(protocol);
});