Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Synchronize content retrieval over the bridge #36072

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;

import com.brentvatne.react.ReactVideoPackage;
Expand Down Expand Up @@ -49,6 +50,7 @@
import org.reactnative.maskedview.RNCMaskedViewPackage;

import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.mobile.ReactNativeAztec.ReactAztecPackage;
import org.wordpress.mobile.ReactNativeGutenbergBridge.BuildConfig;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent;
Expand Down Expand Up @@ -667,7 +669,7 @@ public void attachToContainer(ViewGroup viewGroup,
viewGroup.addView(mReactRootView, 0,
new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

if (mReactContext != null) {
if (hasReactContext()) {
setPreferredColorScheme(isDarkMode);
}

Expand Down Expand Up @@ -844,7 +846,7 @@ private void updateContent(String title, String content) {
if (title != null) {
mTitle = title;
}
if (mReactContext != null) {
if (hasReactContext()) {
if (content != null) {
mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().setHtmlInJS(content);
}
Expand All @@ -854,72 +856,96 @@ private void updateContent(String title, String content) {
}
}

public interface OnGetContentTimeout {
void onGetContentTimeout(InterruptedException ie);
public interface OnGetContentInterrupted {
void onGetContentInterrupted(InterruptedException ie);
}

public CharSequence getContent(CharSequence originalContent, OnGetContentTimeout onGetContentTimeout) {
if (mReactContext != null) {
public synchronized CharSequence getContent(CharSequence originalContent,
OnGetContentInterrupted onGetContentInterrupted) {
if (hasReactContext()) {
mGetContentCountDownLatch = new CountDownLatch(1);

mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().getHtmlFromJS();

try {
mGetContentCountDownLatch.await(10, TimeUnit.SECONDS);
boolean success = mGetContentCountDownLatch.await(10, TimeUnit.SECONDS);
if (!success) {
AppLog.e(T.EDITOR, "Timeout reached before response from requestGetHtml.");
}
} catch (InterruptedException ie) {
onGetContentTimeout.onGetContentTimeout(ie);
onGetContentInterrupted.onGetContentInterrupted(ie);
}

return mContentChanged ? (mContentHtml == null ? "" : mContentHtml) : originalContent;
} else {
// TODO: Add app logging here
AppLog.e(T.EDITOR, "getContent was called when there was no React context.");
}

return originalContent;
}

public CharSequence getTitle(OnGetContentTimeout onGetContentTimeout) {
if (mReactContext != null) {
/** This method retrieves both the title and the content from the Gutenberg editor by the emission of a single
* event. This is useful to avoid redundant events, since {@link #getContent} already retrieves the title as well,
* using same event, and also shares the same mechanism to suspend execution until a response is received (or a
* timeout is reached).
* @param originalContent fallback content to return in case the timeout is reached, or the thread is interrupted
* @param onGetContentInterrupted callback to invoke if thread is interrupted before the timeout
* @return A Pair of CharSequence with the first being the title and the second being the content
*/
public synchronized Pair<CharSequence, CharSequence> getTitleAndContent(CharSequence originalContent,
OnGetContentInterrupted onGetContentInterrupted) {
if (hasReactContext()) {
mGetContentCountDownLatch = new CountDownLatch(1);

mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().getHtmlFromJS();

try {
mGetContentCountDownLatch.await(10, TimeUnit.SECONDS);
boolean success = mGetContentCountDownLatch.await(10, TimeUnit.SECONDS);
if (!success) {
AppLog.e(T.EDITOR, "Timeout reached before response from requestGetHtml.");
}
} catch (InterruptedException ie) {
onGetContentTimeout.onGetContentTimeout(ie);
onGetContentInterrupted.onGetContentInterrupted(ie);
}

return mTitle == null ? "" : mTitle;
return new Pair<>(
mTitle == null ? "" : mTitle,
mContentChanged ? (mContentHtml == null ? "" : mContentHtml) : originalContent
);
} else {
// TODO: Add app logging here
AppLog.e(T.EDITOR, "getTitleAndContent was called when there was no React context.");
}

return "";
return new Pair<>("", originalContent);
}

public boolean triggerGetContentInfo(OnContentInfoReceivedListener onContentInfoReceivedListener) {
if (mReactContext != null && (mGetContentCountDownLatch == null || mGetContentCountDownLatch.getCount() == 0)) {
if (hasReactContext() && (mGetContentCountDownLatch == null || mGetContentCountDownLatch.getCount() == 0)) {
if (!mIsEditorMounted) {
onContentInfoReceivedListener.onEditorNotReady();
return false;
}

mGetContentCountDownLatch = new CountDownLatch(1);

mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().getHtmlFromJS();

new Thread(new Runnable() {
@Override public void run() {
try {
mGetContentCountDownLatch.await(5, TimeUnit.SECONDS);
if (mContentInfo == null) {
// We need to synchronize access to (and overwriting of) the latch to avoid race conditions
synchronized (WPAndroidGlueCode.this) {
mGetContentCountDownLatch = new CountDownLatch(1);

mRnReactNativeGutenbergBridgePackage.getRNReactNativeGutenbergBridgeModule().getHtmlFromJS();

try {
boolean success = mGetContentCountDownLatch.await(5, TimeUnit.SECONDS);
if (!success) {
AppLog.e(T.EDITOR, "Timeout reached before response from requestGetHtml.");
}
if (mContentInfo == null) {
onContentInfoReceivedListener.onContentInfoFailed();
} else {
onContentInfoReceivedListener.onContentInfoReceived(mContentInfo.toHashMap());
}
} catch (InterruptedException ie) {
onContentInfoReceivedListener.onContentInfoFailed();
} else {
onContentInfoReceivedListener.onContentInfoReceived(mContentInfo.toHashMap());
}
} catch (InterruptedException ie) {
onContentInfoReceivedListener.onContentInfoFailed();
}
}
}).start();
Expand Down
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
- [**] [Image block] Add ability to quickly link images to Media Files and Attachment Pages [#34846]
- [*] Fixed a race condition when autosaving content (Android) [#36072]

## 1.65.0
- [**] Search block - Text and background color support [#35511]
Expand Down