MeatPopsicle
MeatPopsicle

Reputation: 992

Manually trigger cut/copy/paste in android webview

I'm building a little browser app using android webview and I've been using window.getSelection() in javascript to get the nature of any text selected by the user and show a custom context menu based on the type of the selection i.e. whether it's a range, a carat, whether it's in a contenteditable etc.

This works fine unless the selection is in an iframe, then the browser security measures kick in and prevent me sniffing what has been selected using window.getSelection(). How can I workaround this?

Ideally I need a way to get better information about what was selected from the webview or if that's not possible I need a way to sniff whether the selection occurred in an iframe so I can disable my custom context menu logic and fallback to the default android context menu.

UPDATE/FURTHER CLARIFICATION 07/05/2019:

Seems I wasn't clear enough in my initial description...

My goal is to have a visually and functionally custom menu when selecting content in the webview that can cut/copy/paste as the standard context menu does in any part of the page/iframes etc. e.g.

example custom menu

I realised my original approach using javascript to detect the type of selection and to perform the cut/copy/paste was wrong because it will be blocked by cross origin security in iframes.

What I need is a native android/webview based approach. I've discovered that I can sniff the type of selection in the webview by looking at the items in mode.getMenu() on onActionModeStarted. This will allow me to show the correct buttons in my custom menu UI but I have been unable to manually trigger the same logic that gets called when cut/copy/paste is clicked. I thought I found the solution with webView.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CUT, null); but this doesn't work for some reason so I guess my question really is how can I manually trigger cut/copy/paste on the selected text from webview without using javascript? or any other approach that will allow me to have a custom selection menu with lots of options based on what was selected without hitting the browser security limitations?

Upvotes: 2

Views: 3386

Answers (4)

MeatPopsicle
MeatPopsicle

Reputation: 992

Okay I figured out how roughly how to do this.

Step 1) In your activity, override onActionModeStarted and check the menu items available in the default context menu. This gives you a clue as to what the type of selection is and which buttons you will need to show in your custom menu. Also it gives you a reference to the item ID which you can use to later to trigger the action e.g.

systemSelectionMenu = mode.getMenu(); // keep a reference to the menu
MenuItem copyItem = systemSelectionMenu.getItem(0); // fetch any menu items you want
copyActionId = copyItem.getItemId(); // store reference to each item you want to manually trigger

Step 2) Instead of clearing the menu, use setVisible() to hide each menu item you want a custom button for e.g.

copyItem.setVisible(false); 

Step 3) In your custom button onclick event you can trigger the copy action using:

myActivity.systemSelectionMenu.performIdentifierAction(myActivity.copyActionId, 0)

Upvotes: 2

Pavel Shirobok
Pavel Shirobok

Reputation: 307

Main problem is the window.getSelection() will return selection only for the main context/window. As iframe is the other window and other context, you should call getSelection() from iframe which is "current".

Upvotes: 0

Creative87
Creative87

Reputation: 125

This issue varying from browser to other if it works with internet explorer so it may fall with chrome Try this

App.util.getSelectedText = function(frameId) {
var frame = Ext.getDom(frameId);
var frameWindow = frame.contentWindow;
var frameDocument = frameWindow.document;

if (frameDocument.getSelection) {
    return frameDocument.getSelection();
}
else if (frameDocument.selection) {
    return frameDocument.selection.createRange().text;
}
};

Hope it runs fine

Upvotes: 0

teimurjan
teimurjan

Reputation: 1975

You can retrieve iframe's selection only if it has the same origin. Otherwise, you have no chances to track any iframe's events(clicks, touches, key presses, etc.).

const getSelectedText = (win, doc) => {    
  const isWindowSelectionAvailable = win && typeof win.getSelection != "undefined";
  if (isWindowSelectionAvailable) {
    return win.getSelection().toString();
  }

  const hasDocumentSelection = doc && typeof doc.selection != "undefined" && doc.selection.type == "Text";
  if (hasDocumentSelection) {
    return doc.selection.createRange().text;
  }

  return '';
}

const doIfTextSelected = (win, doc, cb) => () => {
  const selectedText = getSelectedText(win, doc);
  if (selectedText) {
      cb(selectedText);
  }
}

const setupSelectionListener = (win, doc, cb) => {
  doc.onmouseup = doIfTextSelected(win, doc, cb);
  doc.onkeyup = doIfTextSelected(win, doc, cb);
}

const getIframeWinAndDoc = (iframe) => {
  try {
    const doc = iframe.contentDocument || iframe.contentWindow.document;
    const win = iframe.contentWindow || iframe.contentDocument.defaultView;

    return { win, doc };
  } catch (e) {
    console.error(`${e}`);
    
    return {};
  }
}

const callback = console.log;

setupSelectionListener(window, document, callback);

document.querySelectorAll('iframe').forEach(iframe => {
  const { win, doc } = getIframeWinAndDoc(iframe, console.log);
  
  // Only for same origin iframes due to https://en.wikipedia.org/wiki/Same-origin_policy
  if (win && doc) {
    setupSelectionListener(win, doc, callback);
  }
})
<h3>Select me</h3>

<div class="container">
  <iframe src="https://teimurjan.github.io"></iframe>
</div>

Upvotes: 1

Related Questions