在 android webview 中手动触发 cut/copy/paste

Manually trigger cut/copy/paste in android webview

我正在使用 android webview 构建一个小浏览器应用程序,我一直在 javascript 中使用 window.getSelection() 来获取用户选择的任何文本的性质并显示基于选择类型的自定义上下文菜单,即它是否是范围、克拉、是否在 contenteditable 等中。

除非选择是在 iframe 中,否则这工作正常,然后浏览器安全措施开始并阻止我嗅探使用 window.getSelection() 选择的内容。我该如何解决这个问题?

理想情况下,我需要一种方法来获取有关从 webview 选择的内容的更好信息,或者如果这不可能,我需要一种方法来嗅探选择是否发生在 iframe 中,以便我可以禁用我的自定义上下文菜单逻辑和回退到默认的 android 上下文菜单。

UPDATE/FURTHER 2019 年 7 月 5 日澄清:

看来我最初的描述还不够清楚...

我的目标是在 web 视图中选择内容时在视觉和功能上有一个 自定义 菜单,可以 cut/copy/paste 作为标准上下文菜单在page/iframes 等等 例如

我意识到我最初使用 javascript 检测选择类型并执行 cut/copy/paste 的方法是错误的,因为它会被 iframe 中的跨源安全性阻止。

我需要的是一种基于原生 android/webview 的方法。我发现我可以通过查看 onActionModeStarted 上的 mode.getMenu() 中的项目来嗅探 web 视图中的选择类型。这将允许我在自定义菜单 UI 中显示正确的按钮,但我无法手动触发单击 cut/copy/paste 时调用的相同逻辑。我以为我找到了 webView.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CUT, null); 的解决方案,但由于某种原因这不起作用所以我想我的问题真的是如何在不使用 [=49= 的情况下手动触发来自 webview 的选定文本的 cut/copy/paste ]?或任何其他方法,允许我拥有一个自定义选择菜单,其中包含基于所选内容的大量选项,而不会触及浏览器安全限制?

您只能检索 iframe 的选择,前提是它具有相同的来源。否则,您没有机会跟踪任何 iframe 的事件(点击、触摸、按键等)。

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>

这个问题因浏览器而异,如果它适用于 Internet Explorer,所以它可能与 chrome 试试这个

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;
}
};

希望一切顺利

主要问题是 window.getSelection() 将 return 选择仅用于主要 context/window。由于 iframe 是另一个 window 和其他上下文,您应该从 "current" 的 iframe 调用 getSelection()

好吧,我大概知道怎么做了。

步骤 1) 在您的 activity 中,覆盖 onActionModeStarted 并检查默认上下文菜单中可用的菜单项。这为您提供了有关选择类型是什么以及需要在自定义菜单中显示哪些按钮的线索。它还为您提供了对项目 ID 的引用,您可以在以后使用它来触发操作,例如

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

第 2 步)不要清除菜单,而是使用 setVisible() 隐藏您想要自定义按钮的每个菜单项,例如

copyItem.setVisible(false); 

步骤 3) 在您的自定义按钮 onclick 事件中,您可以使用以下方式触发复制操作:

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