Open Bug 1603985 Opened 5 years ago Updated 1 year ago

document.execCommand('paste') not working correctly on web extension background page

Categories

(WebExtensions :: General, defect, P3)

defect

Tracking

(firefox72 affected, firefox73 affected, firefox74 affected)

ASSIGNED
Tracking Status
firefox72 --- affected
firefox73 --- affected
firefox74 --- affected

People

(Reporter: toasted.nutbread, Assigned: robwu)

References

Details

document.execCommand('paste') does not seem to be working correctly on web extension background pages despite clipboardRead permission being granted. Depending on the HTML, the command will sometimes work and sometimes not work. Invalid HTML seems to allow it to work.

Using this bg.js script for testing:

function checkClipboard() {
  const target = document.querySelector('#target');
  target.innerText = '';
  target.focus();
  document.execCommand('paste');
  const content = target.innerText;
  target.innerText = '<empty>';
  console.log('content', content);
}
setInterval(checkClipboard, 1000);

The following background HTML page does not work (logs empty string):

<html>
  <head>
    <script src="bg.js"></script>
  </head>
  <body>
    <div id="target" contenteditable="true"></div>
  </body>
</html>

Whereas the this HTML page does work (logs clipboard contents):

<html>
  <head>
    <script src="bg.js"></script>
  </head>
  <body>
    <div id="target" contenteditable="true" />
  </body>
</html>

Note the incorrect ending tag on the <div>.

Example repository demonstrating the bug can be found at:
https://github.com/toasted-nutbread/firefox-clipboard-paste-bug

For more context, there is at least one extension that relies on this buggy behavior:
https://github.com/kmltml/clipboard-inserter/blob/924a8f1350f8ba7d8d2adb69612d6ac0e201e97c/bg/index.html#L7

The advantage of this method over navigator.clipboard.readText is that it works on Chrome the same way. Chrome doesn't allow using navigator.clipboard.readText on unfocused pages:
https://stackoverflow.com/questions/56306153/domexception-on-calling-navigator-clipboard-readtext

Component: General → Untriaged
Product: Firefox → WebExtensions
Component: Untriaged → General

The priority flag is not set for this bug.
:jimm, could you have a look please?

For more information, please visit auto_nag documentation.

Flags: needinfo?(jmathies)

Hello,

I have managed to reproduce the issue using the extensions provided via the repository in Comment 0 on the latest Nightly (74.0a1/20200203085242), Beta (73.0b11/20200128001646) and Release (72.0.2/20200117190643) under Windows 10 Pro 64-bit and macOS High Sierra 10.15.

Using the extension and background page from the ext1 folder, no content from the clipboard will be displayed in the console when inspecting the extension.

However, using the extension and background page from the ext2 folder, clipboard content will indeed be displayed in the extension console, as mentioned in Comment 0.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Flags: needinfo?(jmathies)

Would this be related to the copy issue you were looking at? Should this use the new api?

Flags: needinfo?(rob)

This is different from the other issue I looked at (bug 1611799).

The consistent reproduction steps helps with debugging.

With GDB, I found a difference between runs of ext1 and ext2, at:
#0 mozilla::EditorBase::IsSelectionEditable at editor/libeditor/EditorBase.cpp:616
#1 mozilla::PasteCommand::IsCommandEnabled at editor/libeditor/EditorCommands.cpp:461
#2 mozilla::dom::Document::ExecCommand at dom/base/Document.cpp:4788

ext1 (bad): focusNode and anchorNode is the <body> element.
ext2 (good): focusNode and anchorNode is the <div> element.

Now back to the reproduction:

<body>
<div contenteditable />
</body>

Contrary to appearances, <div /> is NOT a self-closing tag. The slash is ignored, and what ends up happening is that the text node with the line break becomes part of the <div> element.
When properly closed, the body has whitespace after the <div>.

Apparently, when whitespace is present after the content-editable <div>, the <div> does not become the sole anchor/focus node, and the parent <body> element is included. This only happens in the background page (hidden browser window), NOT in a browser tab.

A work-around to the problem is to ensure that there is no whitespace after the content-editable node (provided that there are no other bugs...).

Flags: needinfo?(rob)

Minimal reproduction steps:

  1. Create the following extension:

manifest.json from https://github.com/toasted-nutbread/firefox-clipboard-paste-bug/blob/65f29fdbf99bb1d77850d42cd961a5442766b5c9/ext1/manifest.json

bg.html

<script src="bg.js"></script>
<div contenteditable></div>
(with line break and content after div = bug, without anything after </div> = no bug)
// bg.js
window.onload = function() {
  document.querySelector('#target').focus();
  console.log(window.getSelection().focusNode); // Expected: <div>. Actual: <body>
};
  1. Load the extension at about:debugging.
  2. Click on Inspect at the extension to view the console.

Expected:
<div contenteditable>

Actual:
<body>

When bg.html is edited, to remove all content (including line break) after </div> (or simply remove </div> and everything that follows), the expected result happens.

The selection is initialized with the node set to the last container element of the document (**). In ext1's case, that is the <body>.
In ext2's case, that is the <div> element.

The .focus() method does not appear to change the selection when a content-editable element is focused in the background page, which prevents paste from working.
Focusing works for <input> and <textarea> (in bug 1272869) when WebExtensions were moved to a separate process, though we did not investigate why it started to work.

The next step in the investigation is to figure out why .focus() does not work on a content-editable element.

(**) Initialization of selection is as follows:

#0 mozilla::EditorBase::CollapseSelectionToEnd at editor/libeditor/EditorBase.cpp:1099
#1 mozilla::TextEditor::InitEditorContentAndSelection at editor/libeditor/TextEditSubActionHandler.cpp:62
#2 mozilla::HTMLEditor::InitEditorContentAndSelection at editor/libeditor/HTMLEditSubActionHandler.cpp:227
#3 mozilla::HTMLEditor::Init at editor/libeditor/HTMLEditor.cpp:267
#4 nsEditingSession::SetupEditorOnWindow at editor/composer/nsEditingSession.cpp:416
#5 nsEditingSession::MakeWindowEditable at editor/composer/nsEditingSession.cpp:162
#6 mozilla::dom::Document::EditingStateChanged at dom/base/Document.cpp:5469

See Also: → 1272869

To debug nsFocusManager, I ran with: MOZ_LOG=Focus:5

Bad (background page):

[Child 1809667: Main Thread]: D/Focus <<SetFocus begin>>
[Child 1809667: Main Thread]: D/Focus Shift Focus: div
[Child 1809667: Main Thread]: D/Focus  Flags: 400010 Current Window: (nil) New Window: 0x7f80732e63e0 Current Element: (nil)
[Child 1809667: Main Thread]: D/Focus  In Active Window: 0 In Focused Window: 0 SendFocus: 0
[Child 1809667: Main Thread]: D/Focus <<SetFocus end>>

Good (same page, but in a tab):

[Child 1809667: Main Thread]: D/Focus <<SetFocus begin>>
[Child 1809667: Main Thread]: D/Focus Shift Focus: div
[Child 1809667: Main Thread]: D/Focus  Flags: 400010 Current Window: 0x7f80732e6060 New Window: 0x7f80732e6060 Current Element: (nil)
[Child 1809667: Main Thread]: D/Focus  In Active Window: 1 In Focused Window: 1 SendFocus: 1
[Child 1809667: Main Thread]: D/Focus <<Blur begin>>
[Child 1809667: Main Thread]: D/Focus Element (none) has been blurred
[Child 1809667: Main Thread]: D/Focus Update Caret: 0 1
[Child 1809667: Main Thread]: D/Focus <<Focus begin>>
[Child 1809667: Main Thread]: D/Focus Element div has been focused
[Child 1809667: Main Thread]: D/Focus  from html
[Child 1809667: Main Thread]: D/Focus  [Newdoc: 0 FocusChanged: 1 Raised: 0 Flags: 400010]
[Child 1809667: Main Thread]: D/Focus Update Caret: 1 0
[Child 1809667: Main Thread]: D/Focus <<SetFocus end>>

The Update Caret message in the good case is missing from the bad case. This part triggers the update of the selection.
nsFocusManager::MoveCaretToFocus is called by nsFocusManager::UpdateCaret, and usually called by nsFocusManager::focus, but skipped when the window is not marked as active.

To update the selection, a MoveCaretToFocus call needs to be added around this place: https://searchfox.org/mozilla-central/rev/5a10be606f2d76ef22f1f44565749490de991d35/dom/base/nsFocusManager.cpp#1471-1483

This is not enough: When an element to focus has no child nodes, the caret is placed before it. ... but in case of a contentEditable element, placing the caret before it means that the focus moves to the <body> element.

Assignee: nobody → rob
Status: NEW → ASSIGNED
Priority: -- → P3
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.