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

Code from the injectedJavaScriptBeforeContentLoaded prop runs AFTER the web code. #1609

Closed
Kreozot opened this issue Sep 3, 2020 · 31 comments

Comments

@Kreozot
Copy link

Kreozot commented Sep 3, 2020

Bug description:

According to official docs of react-native-webview (The injectedJavaScriptBeforeContentLoaded prop):

This is a script that runs before the web page loads for the first time. It only runs once, even if the page is reloaded or navigated away. This is useful if you want to inject anything into the window, localstorage, or document prior to the web code executing.

But we can see that the script on the page runs first, and only then runs the code from the injectedJavaScriptBeforeContentLoaded prop.

The setup is:

To Reproduce:

  1. git clone git@github.com:Kreozot/webview-scripts-test.git
  2. cd webview-scripts-test
  3. yarn
  4. yarn run android

Expected behavior:

Alert "injectedJavaScriptBeforeContentLoaded" shows before "script executed".

Screenshots/Videos:

Alert "injectedJavaScriptBeforeContentLoaded" shows after "script executed".

Environment:

  • OS: Android
  • OS version: 9
  • react-native version: 0.63.2
  • react-native-webview version: 10.8.3
@Kreozot
Copy link
Author

Kreozot commented Sep 3, 2020

UPD: sometimes it works as expected, but like 50/50.

@renwenci
Copy link

i have the same issue on IOS 13.6.1, anyone has ideas?

@roscorcoran
Copy link

roscorcoran commented Sep 23, 2020

Same or similar issues here, however on Android the Javascript injected via injectedJavaScriptBeforeContentLoaded never runs, even when Javascript injected via injectedJavaScript does run.
IOS does run

Device: Pixel 2 in Emulator
OS: Android
OS version: android 9
react-native version: 0.61.2
react-native-webview version: ^10.3.2

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Nov 24, 2020
@Kreozot
Copy link
Author

Kreozot commented Nov 24, 2020

Dear bot, please don't close this issue because it's still an issue.

@github-actions github-actions bot removed the Stale label Nov 28, 2020
@ArthurSampaio
Copy link

Hi @Kreozot, did you find an away or workaround to solve this?

@Kreozot
Copy link
Author

Kreozot commented Jan 13, 2021

Hi @Kreozot, did you find an away or workaround to solve this?

I've avoided injecting. Instead I just put some async scripts loading in my HTML template:

...
      <body>
        <script>
          // Function that runs some script and then calling the callback
          function evalScript(filename, callback) {
            var head=document.getElementsByTagName("head")[0];
            var script=document.createElement('script');
            script.src=filename;
            script.type='text/javascript';
            script.onload=callback;
            head.appendChild(script);
          }

          evalScript("file:///android_asset/firstScript.js", function () { // will be executed before the second
            evalScript("file:///android_asset/secondScript.js"); // will be executed after the first
          });
        </script>
      </body>

Yes, this looks very dirty - I'm not proud of it - but it works :)

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@Bardiamist
Copy link

Bardiamist commented Apr 1, 2021

I faced with it.
I created repro https://github.com/Bardiamist/injectedjs and I see issue already exist here.
It is on Android (on iOS it works as expected).

@allan-simon
Copy link

I confirm it still exist (with the same device it sometimes execute before all script, sometime after the script in but before the script in etc. sounds like there's a missing /lock or sync issue causing the injection execution to be based purely on luck in android ) , @Titozzz @chiaramooney can this issue be reopened ?

Also do you mind if I open a PR in the documentation to warn about this current issue waiting for it to solved?

@Titozzz Titozzz reopened this Jul 19, 2021
@github-actions github-actions bot removed the Stale label Jul 20, 2021
@EduardoNogueira
Copy link

I also can confirm that this is still an issue, it seems that the injectedJavaScriptBeforeContentLoaded prop isn't working properly for webviews with real hosted websites, more details here. Does anyone know a workaround that doesn't require changing the website source code?

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Nov 15, 2021
@allan-simon
Copy link

still searching for solution (a documentation warning could be enough )

@github-actions github-actions bot removed the Stale label Nov 16, 2021
@Bardiamist
Copy link

Bardiamist commented Nov 16, 2021

still searching for solution (a documentation warning could be enough )

My workaround as old as the world

<WebView
  injectedJavaScriptBeforeContentLoaded={`window.injectedSettings = ${JSON.stringify(injectedSettings)}; true;`}
  ...
/>

const wait = async (ms: number) => new Promise((resolve) => {
  setTimeout(resolve, ms);
});

const initOnReady = async () => {
  while (window.injectedSettings == null) {
    // eslint-disable-next-line no-await-in-loop
    await wait(100);
  }
  ...
};

initOnReady();

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@hkar19
Copy link

hkar19 commented Apr 26, 2022

if you are the same person (like me) running React native webview@9.4.0, and wondering why injectedJavaScriptBeforeContentLoaded did not run in android, please apply this diff below.

The idea is:

  • the props passed to RNCWebview was not handled in android, so we handle it ourselves by copying the codes from master (currently 11.18.1) and apply it where we needed it.
  • we wanted this script to run onPageStarted

react-native-webview+9.4.0.patch

--- a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
+++ b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
@@ -396,6 +396,12 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
     ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
   }
 
+  @ReactProp(name = "injectedJavaScriptBeforeContentLoaded")
+  public void setInjectedJavaScriptBeforeContentLoaded(WebView view, @Nullable String injectedJavaScriptBeforeContentLoaded) {
+    ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoaded(injectedJavaScriptBeforeContentLoaded);
+  }
+
+
   @ReactProp(name = "messagingEnabled")
   public void setMessagingEnabled(WebView view, boolean enabled) {
     ((RNCWebView) view).setMessagingEnabled(enabled);
@@ -747,6 +753,9 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
       super.onPageStarted(webView, url, favicon);
       mLastLoadFailed = false;
 
+      RNCWebView reactWebView = (RNCWebView) webView;
+      reactWebView.callInjectedJavaScriptBeforeContentLoaded();
+
       dispatchEvent(
         webView,
         new TopLoadingStartEvent(
@@ -1000,6 +1009,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
   protected static class RNCWebView extends WebView implements LifecycleEventListener {
     protected @Nullable
     String injectedJS;
+    protected @Nullable
+    String injectedJSBeforeContentLoaded;
     protected boolean messagingEnabled = false;
     protected @Nullable
     String messagingModuleName;
@@ -1081,10 +1092,15 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
       injectedJS = js;
     }
 
+    public void setInjectedJavaScriptBeforeContentLoaded(@Nullable String js) {
+      injectedJSBeforeContentLoaded = js;
+    }
+
     protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
       return new RNCWebViewBridge(webView);
     }
 
+
     protected void createCatalystInstance() {
       ReactContext reactContext = (ReactContext) this.getContext();
 
@@ -1135,6 +1151,14 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
       }
     }
 
+    public void callInjectedJavaScriptBeforeContentLoaded() {
+      if (getSettings().getJavaScriptEnabled() &&
+        injectedJSBeforeContentLoaded != null &&
+        !TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
+        evaluateJavascriptWithFallback("(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();");
+      }
+    }
+
     public void onMessage(String message) {
       ReactContext reactContext = (ReactContext) this.getContext();
       RNCWebView mContext = this;

@TheAlmightyBob
Copy link
Collaborator

Reopening because:

  1. The multiple folks chiming in on this issue here
  2. The original PR (Add support for injectedJavaScriptBeforeContentLoaded on Android #1099) itself acknowledged that the solution didn't seem 100% reliable
  3. While I haven't been able to repro any problems myself, I tossed an experiment in an app for others to try and there were indeed some failures.

That said, it's unclear to me if there's a possible "fix" here or if this is just an Android WebView limitation we need to warn folks about.

I did see this comment (#1099 (comment)) suggesting an alternative approach, but that seems quite complex/hacky/risky for general use. Could be a useful workaround (via custom Android component) for folks who really need it though.

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Jan 18, 2023
@TheAlmightyBob
Copy link
Collaborator

'sup bot. Still a thing.

@github-actions github-actions bot removed the Stale label Jan 19, 2023
@frags51
Copy link

frags51 commented Mar 15, 2023

We are observing that this sometimes does not work for iOS as well. Has it been observed by anyone else?

@TheAlmightyBob
Copy link
Collaborator

I've never seen nor heard of it failing on iOS

@github-actions
Copy link

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@thenxkk
Copy link

thenxkk commented Nov 3, 2023

Similar issues here, On Android the Javascript injected via injectedJavaScriptBeforeContentLoaded/injectedJavaScript partly never runs.
Some javascript code about window.localStorage.setItem("xxx","xxx") didn't work but other code like alert('xxx') work

Device: Samsung Galaxy A34 5G
OS: Android
OS version: android 9
react-native version: 0.71.13
react-native-webview version: 13.6.2

@thenxkk
Copy link

thenxkk commented Nov 3, 2023

Similar issues here, On Android the Javascript injected via injectedJavaScriptBeforeContentLoaded/injectedJavaScript partly never runs. Some javascript code about window.localStorage.setItem("xxx","xxx") didn't work but other code like alert('xxx') work

Device: Samsung Galaxy A34 5G OS: Android OS version: android 9 react-native version: 0.71.13 react-native-webview version: 13.6.2

OS version: android 13

@TheAlmightyBob
Copy link
Collaborator

TheAlmightyBob commented Nov 4, 2023

Reopening for now, but I can't keep doing that forever, and it's worth noting that version 13.6.0 adds a new injectJavaScriptObject specifically for Android as another option for some scenarios affected by this. (#2960)

@thenxkk I'm not aware of any issues with the normal injectedJavaScript feature not running on Android. You should probably open a separate issue for that and investigate further.

Copy link

github-actions bot commented Jan 4, 2024

Hello 👋, this issue has been opened for more than 2 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

@ivankdev
Copy link

ivankdev commented Apr 3, 2024

@TheAlmightyBob what could you say about this issue #3377

@ReedVivid
Copy link

This is still an issue on 13.8.6. injectedJavaScriptBeforeContentLoaded only works 50% of the time on Android.

@tarasovladislav
Copy link

tarasovladislav commented May 10, 2024

Did anyone find a workaround? Android is 50\50... perfectly works on ios

@17Amir17
Copy link
Contributor

17Amir17 commented Jul 8, 2024

Not sure if related, but this seems to happen to me only when launching the app for the first time, after that it seems to work fine.

@Ishaan-flyfin
Copy link

this issue was happening to us only when launching the app for the first time in android

after spending lot of time on it we realised that in android injectedJavaScript gets called after web code gets executed and in ios injectedJavaScript was getting called before webcode was executed

before fix eg:

app code: this is what we were injecting in webview

 const injectedJSCodeOnMount = `
  window.localStorage.setItem('xyz', '${xyz}');
  window.localStorage.setItem('abc', 'true');
  `;

web code: this is how we were reading it in web

 useEffect(() => {
    const xyz = localStorage.getItem('xyz');
    const abc = localStorage.getItem('abc') === 'true';
    if (xyz && abc) {
     // use xyz and abc
    }
  }, []);

for android the xyz was coming as empty in web while reading and in iOS xyz was coming as expected


Solution

  • we dispatched an event as soon as injectedJavaScript get called
  • we added a listener in web to handle the event sent from app

eg
App code:

const sendEventToWeb = (event: WebEvent, data = '') =>
  `(function() {
    ${
      Platform.OS === 'ios' ? 'window' : 'document'
    }.dispatchEvent(new MessageEvent('${event}', {data: ${JSON.stringify(
    data,
  )}}));
  })()`;


  const injectedJSCodeOnMount = `
  window.localStorage.setItem('xyz', '${xyz}');
  window.localStorage.setItem('abc', 'true');
  ${sendEventToWeb('SAVE_WEB_VIEW_DATA'};
  `;

Web code:

  const saveDataFromWebView = useCallback(() => {
    const xyz = localStorage.getItem('xyz');
    const abc = localStorage.getItem('abc') === 'true';

    if (xyz && abc) {
      // use xyz and abc here
    }
  }, []);

  /**
   * this handles if JS code gets injected before web code runs
   * eg: in case of iOS
   */
  useEffect(() => {
    saveDataFromWebView();
  }, [saveDataFromWebView]);

  /**
   * this handles if JS code gets injected after web code runs
   * eg: in case of Android
   */
  useEffect(() => {
    document.addEventListener('SAVE_WEB_VIEW_DATA', saveDataFromWebView);

    return () => {
      document.removeEventListener('SAVE_WEB_VIEW_DATA', saveDataFromWebView);
    };
  }, [saveDataFromWebView]);

the web code handles for Js getting injected before/after reading on web

contributors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests