Is there a flexible way to change the contents of an edited item?

Theme:

I am creating a Google Chrome extension that interacts with web pages through script content and an event page.

I have context menu options that appear if a user clicks on an item with an editable category using the chrome.contextMenus API ,

Options act as shortcuts for commonly entered text. When the user presses a parameter, some text is placed inside the element at the cursor position. If the user selects the text, it will be deleted.


Problem:

Not all editable items can be changed equally.

If the element is simple textarea , the desired result can be achieved by implementing this solution:

However, I cannot assume that I am interacting with regular textarea .

Possible nuances:

  • The text area hidden inside the Iframe , which complicates the process of finding an element to interact with ( document.activeElement can return an Iframe , rather than an element that actually contains text).

  • <textarea> not <textarea> / <input> at all, but rather a contentEditable <div> . In this case, the .value approach will not work.

So, I am looking for a flexible way to do this to elegantly handle all extreme cases.


Some solutions I tried:

  • option 1:
    I originally planned to save the value to the system clipboard. Then I could just use document.execCommand('paste') to modify the element. However, after trying this, this approach seems to have the same drawbacks as my original approach. (See this question )

    In addition, this approach will delete everything that was on the clipboard before the operation. This is undesirable, and any solution using this approach should work.

  • option 2:
    Another option I examined is to send keyboard events for each character in a string. However, with this solution, you still encounter an Iframe problem, and this does not allow you to use special Unicode characters. ┻━┻ (ヽ (`D ') ノ (┻━┻
+6
source share
1 answer

Your problem consists of two subtasks:

  • Define the target element of the contextmenu action.
  • Insert a custom piece of text in the carriage (remove any selection, if present).

Subtask 1: Target Identification

  • If crbug.com/39507 is allowed, then getting the item is easy. This feature request has been nearly 5 years without any progress, so don't count on it. Alternative methods require that you break Problem 1 in the following two sub-tasks: Identify the target frame, and then select the target DOM element.

There are several APIs that help identify the frame (use a combination of them, choose which combination is best for your situation):

  • The contextMenus.onClicked event provides the tab identifier ( tab.id ) as a property in the tabs.Tab instance and the frame URL ( frameUrl ) in a separate object.
  • The chrome.tabs.executeScript method can directly run a script in a frame.
    (Currently, only the top-level frame works, or all frames aimed at a specific frame - crbug.com/63979 , scheduled for Chrome 42).
    Until a specific frame is targeted, you can paste the contents of the script into each frame and compare the URL with frameUrl (or use a combination of the following methods).
  • Assuming you've already pasted the script content using the chrome.runtime.onMessage listener, use chrome.tabs.sendMessage to send a message to a specific frame identified by frameId (starting with Chrome 41).
  • Use the chrome.webNavigation.getAllFrames method to get a list of all the frames in a tab for a given tabId , then get the frameId target frame by filtering the list of frames using the famous frameUrl .
  • (in the future (Chrome 42?), contextMenus.onClicked will get frameId ).

Well, assuming you have the correct frame, you can simply use document.activeElement to get the target element, because the input elements focus on the click.

Subtask 2: Insert a piece of text in a carriage

If the target element is <textarea> or <input> , you can simply use

 // Assume: elem is an input or textarea element. var YOURSTRING = 'whatever'; var start = elem.selectionStart; var end = elem.selectionEnd; elem.value = elem.value.slice(0, start) + YOURSTRING + elem.value.substr(end); // Set cursor after selected text elem.selectionStart = start + YOURSTRING.length; elem.selectionEnd = elem.selectionStart; 

Otherwise, you need to know if there is an editing element for the content, and if so, delete any selection if it exists, and finally put the text you want there.

 var elem = document.activeElement; if (elem && elem.isContentEditable) { // YOURSTRING as defined before var newNode = document.createTextNode(YOURSTRING); var sel = window.getSelection(); // Remove previous selection, if any. sel.deleteFromDocument(); // If there is no range in the selection, add a new one, with the // caret set to the end of the input element to avoid the next error: //"Failed to execute 'getRangeAt' on 'Selection': 0 is not a valid index." if (sel.rangeCount === 0) { sel.addRange(document.createRange()); sel.getRangeAt(0).collapse(elem, 1); } // Insert new text var range = sel.getRangeAt(0); range.insertNode(newNode); // Now, set the cursor to the end of your text node sel.collapse(newNode, 1); } 

Relevant documentation for the web platform APIs used in the last example:

+6
source

Source: https://habr.com/ru/post/981313/


All Articles