Skip to content

Commit

Permalink
[RNMobile] Fix UBE Text Selection Bug on Android (#34668)
Browse files Browse the repository at this point in the history
* Load new custom JS file in UBE

With this commit, a new JS file is loaded in 'GutenbergWebViewActivity.java'. This new file will have our custom JS, which will load when the UBE is open.

* Detect when dropdown menus are active

An event listener is added as part of this commit, which will listen for changes in focus in the UBE. In addition, an if statement that checks for the specific elements we're interested in (the toolbars dropdown menus) has been added. This if statement will be fleshed out in future commits.

* Remove toolbar by resetting text selection

The text selection is reset by utilising JavaScript's setBaseAndExtent' method. This has the effect of removing the text selection toolbar when a dropdown menu is pressed, while maintaining the original selection.

See here for more details on setBaseAndExtent: https://developer.mozilla.org/en-US/docs/Web/API/Selection/setBaseAndExtent

* Rename 'custom-js' file for clarity

This PR renames the 'custom-js' file to 'editor-behavior-overrides.js' in an attempt to clarify its purpose.

* Add clarifying comment to new JS file

As this is a somewhat hacky and unintuitive fix, this commit adds a clarifying comment along with a link back to the PR for more details.

* Improve if/else logic by adding 'selected' check

The if/else statement was only checking whether specific buttons were active before resetting the text selection. With this commit, an extra check for whether text is actually selected has been added. The code for resetting the selection will therefore only be run when necessary (i.e. it's not necessary if there's no selection).

* Access 'activeElement' via 'document'

With this commit, changes are made to access the 'activeElement' via 'document', rather than 'window'. Accessing directly via 'window' was not working as expected. It was necessary to still reference 'window' to bypass lint errors.

* Add back 'activeElement' selector

This was accidentally removed with the last commit.

* Update CHANGELOG

* Notify Android code when certain menus are tapped

Previously the selected text was reset on the JavaScript side of the codebase (using the 'setBaseAndExtent' method). This commit refactors the code so that the Android side of the codebase is notified instead, via the newly created 'hideTextSelectionContextMenu' method. The aim here is to make use of Android's built-in functions around ActionMode, in order to make the code and functionality more stable.

* Update comment to clarify new code changes

* Hide text selection toolbar when selecting certain items

This commit taps into Android's ActionMode to hide the text selection toolbar whenever the 'hideTextSelectionContextMenu' function is called.

More specficially, it uses the 'finish' method: https://developer.android.com/reference/android/view/ActionMode#finish()

* Add missing variable

I accidentally ommitted the 'mActionMode' variable from previous commits.

* Add listener for hide text selection context menu event

* Listen to click events instead of focus

* Add click listeners for context menu visibility

* Remove commented line

Co-authored-by: Carlos Garcia <fluiddot@gmail.com>
  • Loading branch information
SiobhyB and fluiddot authored Oct 8, 2021
1 parent 7fcbfdb commit 0d2011f
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
Expand All @@ -24,7 +25,7 @@
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import org.wordpress.android.util.AppLog;;
import org.wordpress.android.util.AppLog;
import org.wordpress.mobile.FileUtils;

import java.util.ArrayList;
Expand All @@ -41,6 +42,8 @@ public class GutenbergWebViewActivity extends AppCompatActivity {
private static final String INJECT_LOCAL_STORAGE_SCRIPT_TEMPLATE = "localStorage.setItem('WP_DATA_USER_%d','%s')";
private static final String INJECT_CSS_SCRIPT_TEMPLATE = "window.injectCss('%s')";
private static final String INJECT_GET_HTML_POST_CONTENT_SCRIPT = "window.getHTMLPostContent();";
private static final String INJECT_ON_SHOW_CONTEXT_MENU_SCRIPT = "window.onShowContextMenu();";
private static final String INJECT_ON_HIDE_CONTEXT_MENU_SCRIPT = "window.onHideContextMenu();";
private static final String JAVA_SCRIPT_INTERFACE_NAME = "wpwebkit";

protected WebView mWebView;
Expand All @@ -49,6 +52,7 @@ public class GutenbergWebViewActivity extends AppCompatActivity {
protected TextView mForegroundViewTitle;
protected TextView mForegroundViewSubtitle;
protected boolean mIsRedirected;
protected ActionMode mActionMode = null;

private ProgressBar mProgressBar;
private boolean mIsGutenbergReady;
Expand Down Expand Up @@ -85,6 +89,24 @@ private void showTroubleshootingInstructions() {
mForegroundViewImage.setVisibility(ImageView.VISIBLE);
}

@Override
public void onActionModeStarted(ActionMode mode) {
if (mActionMode == null) {
mActionMode = mode;
}
mWebView.evaluateJavascript(INJECT_ON_SHOW_CONTEXT_MENU_SCRIPT,
value -> AppLog.e(AppLog.T.EDITOR, value));
super.onActionModeStarted(mode);
}

@Override
public void onActionModeFinished(ActionMode mode) {
mActionMode = null;
mWebView.evaluateJavascript(INJECT_ON_HIDE_CONTEXT_MENU_SCRIPT,
value -> AppLog.e(AppLog.T.EDITOR, value));
super.onActionModeFinished(mode);
}

@SuppressLint("SetJavaScriptEnabled")
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down Expand Up @@ -267,6 +289,9 @@ public void onPageFinished(WebView view, String url) {

String injectGutenbergObserver = getFileContentFromAssets("gutenberg-web-single-block/gutenberg-observer.js");
evaluateJavaScript(injectGutenbergObserver);

String behaviorOverrides = getFileContentFromAssets("gutenberg-web-single-block/editor-behavior-overrides.js");
evaluateJavaScript(behaviorOverrides);
}
});
}
Expand Down Expand Up @@ -400,5 +425,14 @@ public void postMessage(String content) {
public void gutenbergReady() {
GutenbergWebViewActivity.this.runOnUiThread(() -> onGutenbergReady());
}

@JavascriptInterface
public void hideTextSelectionContextMenu() {
if (mActionMode != null) {
GutenbergWebViewActivity.this.runOnUiThread(() -> {
GutenbergWebViewActivity.this.mActionMode.finish();
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Listeners for native context menu visibility changes
let isContextMenuVisible = false;
const hideContextMenuListeners = [];

window.onShowContextMenu = () => {
isContextMenuVisible = true;
};
window.onHideContextMenu = () => {
isContextMenuVisible = false;
while ( hideContextMenuListeners.length > 0 ) {
const listener = hideContextMenuListeners.pop();
listener();
}
};

/*
This is a fix for a text selection quirk in the UBE.
It notifies the Android app to dismiss the text selection
context menu when certain menu items are tapped. This is
done via the 'hideTextSelectionContextMenu' method, which
is sent back to the Android app, where the dismissal is
then handle. See PR for further details:
https://github.com/WordPress/gutenberg/pull/34668
*/
window.addEventListener(
'click',
( event ) => {
const selected = document.getSelection();
if ( ! isContextMenuVisible || ! selected || ! selected.toString() ) {
return;
}

// Check if the event is triggered by a dropdown
// toggle button.
const dropdownToggles = document.querySelectorAll(
'.components-dropdown-menu > button'
);
let currentToggle;
for ( const node of dropdownToggles.values() ) {
if ( node.contains( event.target ) ) {
currentToggle = node;
break;
}
}

// Hide text selection context menu when the click
// is triggered by a dropdown toggle.
//
// NOTE: The event propagation is prevented because
// it will be dispatched after the context menu
// is hidden.
if ( currentToggle ) {
event.stopPropagation();
hideContextMenuListeners.push( () => currentToggle.click() );
window.wpwebkit.hideTextSelectionContextMenu();
}
},
true
);
1 change: 1 addition & 0 deletions packages/react-native-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For each user feature we should also add a importance categorization label to i

## Unreleased
- [*] [Embed block] Fix inline preview cut-off when editing URL [#35321]
- [*] [Unsupported Block Editor] Fix text selection bug for Android [#34668]

## 1.63.0
- [**] [Embed block] Add the top 5 specific embed blocks to the Block inserter list [#34967]
Expand Down

0 comments on commit 0d2011f

Please sign in to comment.