Bug 673037 - Allow selecting text in frames. r=mfinkle
authorWes Johnston <wjohnston@mozilla.com>
Thu, 28 Jul 2011 11:01:03 -0700
changeset 73465 5a0b3bee1f8ae617beb5d23e8c69b6a233769dd6
parent 73464 e0c9fca2f500331658650baad2dc31dbaff5cfba
child 73466 7cb0b8e0951fc3c017f2b982c3e7b6a8860fee1b
push id838
push userwjohnston@mozilla.com
push dateThu, 28 Jul 2011 18:01:46 +0000
treeherdermozilla-inbound@35bd1513465d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs673037
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 673037 - Allow selecting text in frames. r=mfinkle
mobile/chrome/content/content.js
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -1327,48 +1327,70 @@ var TouchEventHandler = {
   }
 };
 
 TouchEventHandler.init();
 
 var SelectionHandler = {
   cache: {},
   selectedText: "",
+  contentWindow: null,
   
   init: function() {
     addMessageListener("Browser:SelectionStart", this);
     addMessageListener("Browser:SelectionEnd", this);
     addMessageListener("Browser:SelectionMove", this);
   },
 
   receiveMessage: function(aMessage) {
     let scrollOffset = ContentScroll.getScrollOffset(content);
     let utils = Util.getWindowUtils(content);
     let json = aMessage.json;
 
     switch (aMessage.name) {
       case "Browser:SelectionStart": {
         this.selectedText = "";
 
+        // if this is an iframe, dig down to find the document that was clicked
+        let x = json.x;
+        let y = json.y;
+        let offsetX = 0;
+        let offsetY = 0;
+        let elem = utils.elementFromPoint(x, y, true, false);
+        while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
+          // adjust client coordinates' origin to be top left of iframe viewport
+          let rect = elem.getBoundingClientRect();
+          scrollOffset = ContentScroll.getScrollOffset(elem.ownerDocument.defaultView);
+          offsetX += rect.left;
+          x -= rect.left;
+
+          offsetY += rect.top + scrollOffset.y;
+          y -= rect.top + scrollOffset.y;
+          utils = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+          elem = utils.elementFromPoint(x, y, true, false);
+        }
+        let contentWindow = elem.ownerDocument.defaultView;
+        let currentDocShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell);
+
         // Position the caret using a fake mouse click
-        utils.sendMouseEventToWindow("mousedown", json.x - scrollOffset.x, json.y - scrollOffset.y, 0, 1, 0, true);
-        utils.sendMouseEventToWindow("mouseup", json.x - scrollOffset.x, json.y - scrollOffset.y, 0, 1, 0, true);
+        utils.sendMouseEventToWindow("mousedown", x - scrollOffset.x, y - scrollOffset.y, 0, 1, 0, true);
+        utils.sendMouseEventToWindow("mouseup", x - scrollOffset.x, y - scrollOffset.y, 0, 1, 0, true);
 
         // Select the word nearest the caret
         try {
-          let selcon = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay).QueryInterface(Ci.nsISelectionController);
+          let selcon = currentDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay).QueryInterface(Ci.nsISelectionController);
           selcon.wordMove(false, false);
           selcon.wordMove(true, true);
         } catch(e) {
           // If we couldn't select the word at the given point, bail
           return;
         }
 
         // Find the selected text rect and send it back so the handles can position correctly
-        let selection = content.getSelection();
+        let selection = contentWindow.getSelection();
         if (selection.rangeCount == 0)
           return;
 
         let range = selection.getRangeAt(0).QueryInterface(Ci.nsIDOMNSRange);
         if (!range)
           return;
 
         // Cache the selected text since the selection might be gone by the time we get the "end" message
@@ -1379,44 +1401,49 @@ var SelectionHandler = {
           selection.collapseToStart();
           return;
         }
 
         this.cache = { start: {}, end: {} };
         let rects = range.getClientRects();
         for (let i=0; i<rects.length; i++) {
           if (i == 0) {
-            this.cache.start.x = rects[i].left + scrollOffset.x;
-            this.cache.start.y = rects[i].bottom + scrollOffset.y;
+            this.cache.start.x = rects[i].left + offsetX;
+            this.cache.start.y = rects[i].bottom + offsetY;
           }
-          this.cache.end.x = rects[i].right + scrollOffset.x;
-          this.cache.end.y = rects[i].bottom + scrollOffset.y;
+          this.cache.end.x = rects[i].right + offsetX;
+          this.cache.end.y = rects[i].bottom + offsetY;
         }
 
+        this.contentWindow = contentWindow;
         sendAsyncMessage("Browser:SelectionRange", this.cache);
         break;
       }
 
       case "Browser:SelectionEnd": {
         try {
           // The selection might already be gone
-          content.getSelection().collapseToStart();
+          if (this.contentWindow)
+            this.contentWindow.getSelection().collapseToStart();
+          this.contentWindow = null;
         } catch(e) {}
 
         if (this.selectedText.length) {
           let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
           clipboard.copyString(this.selectedText);
           sendAsyncMessage("Browser:SelectionCopied", { succeeded: true });
         } else {
           sendAsyncMessage("Browser:SelectionCopied", { succeeded: false });
         }
         break;
       }
 
       case "Browser:SelectionMove":
+        if (!this.contentWindow)
+          return;
         // Hack to avoid setting focus in a textbox [Bugs 654352 & 667243]
         let elemUnder = elementFromPoint(json.x - scrollOffset.x, json.y - scrollOffset.y);
         if (elemUnder && elemUnder instanceof Ci.nsIDOMHTMLInputElement || elemUnder instanceof Ci.nsIDOMHTMLTextAreaElement)
           return;
 
         if (json.type == "end") {
           this.cache.end.x = json.x - scrollOffset.x;
           this.cache.end.y = json.y - scrollOffset.y;
@@ -1428,15 +1455,15 @@ var SelectionHandler = {
           utils.sendMouseEventToWindow("mousedown", this.cache.start.x, this.cache.start.y, 0, 1, 0, true);
           // Don't cause a click. A mousedown is enough to move the caret
           //utils.sendMouseEventToWindow("mouseup", this.cache.start.x, this.cache.start.y, 0, 1, 0, true);
           utils.sendMouseEventToWindow("mousedown", this.cache.end.x, this.cache.end.y, 0, 1, Ci.nsIDOMNSEvent.SHIFT_MASK, true);
           utils.sendMouseEventToWindow("mouseup", this.cache.end.x, this.cache.end.y, 0, 1, Ci.nsIDOMNSEvent.SHIFT_MASK, true);
         }
 
         // Cache the selected text since the selection might be gone by the time we get the "end" message
-        this.selectedText = content.getSelection().toString().trim();
+        this.selectedText = this.contentWindow.getSelection().toString().trim();
         break;
     }
   }
 };
 
 SelectionHandler.init();