bug 611741 - Implement copy/paste for text fields in web content r=mfinkle a=2.0+
authorBrad Lassey <blassey@mozilla.com>
Sun, 27 Feb 2011 10:47:27 -0500
changeset 67434 7134694f4edf1535335bc5faec68b5446b084807
parent 67433 978c9fb7d4e320fa3b89806e56be8cf3b78dafa8
child 67435 4cf826600f8a91d0b9fc3d8bb44bb0712c027df8
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, 2.0
bugs611741
bug 611741 - Implement copy/paste for text fields in web content r=mfinkle a=2.0+
mobile/chrome/content/ContextCommands.js
mobile/chrome/content/common-ui.js
mobile/chrome/content/content.js
--- 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);
   });