Skip to content

Commit

Permalink
Run getConstants method statements on main queue
Browse files Browse the repository at this point in the history
Summary:
If a NativeModule requires main queue setup, its `getConstants()` method must be executed on the main thead. The legacy NativeModule infra takes care of this for us. With TurboModules, however, all synchronous methods, including `getConstants()`, execute on the JS thread. Therefore, if a TurboModule requires main queue setup, and exports constants, we must execute its `getConstants()` method body on the main queue explicitly.

**Notes:**
- The changes in this diff should be a noop when TurboModules is off, because `RCTUnsafeExecuteOnMainQueueSync` synchronously execute its block on the current thread if the current thread is the main thread.
- If a NativeModule doens't have the `requiresMainQueueSetup` method, but has the `constantsToExport` method, both NativeModules and TurboModules assume that it requires main queue setup.

## Script
```
const exec = require("../lib/exec");
const abspath = require("../lib/abspath");
const relpath = require("../lib/relpath");
const readFile = (filename) => require("fs").readFileSync(filename, "utf8");
const writeFile = (filename, content) =>
  require("fs").writeFileSync(filename, content);

function main() {
  const tmFiles = exec("cd ~/fbsource && xbgs -n 10000 -l constantsToExport")
    .split("\n")
    .filter(Boolean);

  const filesWithoutConstantsToExport = [];
  const filesWithConstantsToExportButNotGetConstants = [];
  const filesExplicitlyNotRequiringMainQueueSetup = [];

  tmFiles
    .filter((filename) => {
      if (filename.includes("microsoft-fork-of-react-native")) {
        return false;
      }

      return /\.mm?$/.test(filename);
    })
    .map(abspath)
    .forEach((filename) => {
      const code = readFile(filename);
      const relFilename = relpath(filename);

      if (!/constantsToExport\s*{/.test(code)) {
        filesWithoutConstantsToExport.push(relFilename);
        return;
      }

      if (!/getConstants\s*{/.test(code)) {
        filesWithConstantsToExportButNotGetConstants.push(relFilename);
        return;
      }

      if (/requiresMainQueueSetup\s*{/.test(code)) {
        const requiresMainQueueSetupRegex = /requiresMainQueueSetup\s*{\s*return\s+(?<requiresMainQueueSetup>YES|NO)/;
        const requiresMainQueueSetupRegexMatch = requiresMainQueueSetupRegex.exec(
          code
        );

        if (!requiresMainQueueSetupRegexMatch) {
          throw new Error(
            "Detected requiresMainQueueSetup method in file " +
              relFilename +
              " but was unable to parse the method return value"
          );
        }

        const {
          requiresMainQueueSetup,
        } = requiresMainQueueSetupRegexMatch.groups;

        if (requiresMainQueueSetup == "NO") {
          filesExplicitlyNotRequiringMainQueueSetup.push(relFilename);
          return;
        }
      }

      const getConstantsTypeRegex = () => /-\s*\((?<type>.*)\)getConstants\s*{/;
      const getConstantsTypeRegexMatch = getConstantsTypeRegex().exec(code);

      if (!getConstantsTypeRegexMatch) {
        throw new Error(
          `Failed to parse return type of getConstants method in file ${relFilename}`
        );
      }

      const getConstantsType = getConstantsTypeRegexMatch.groups.type;

      const getConstantsBody = code
        .split(getConstantsTypeRegex())[2]
        .split("\n}")[0];

      const newGetConstantsBody = `
  __block ${getConstantsType} constants;
  RCTUnsafeExecuteOnMainQueueSync(^{${getConstantsBody
    .replace(/\n/g, "\n  ")
    .replace(/_bridge/g, "self->_bridge")
    .replace(/return /g, "constants = ")}
  });

  return constants;
`;

      writeFile(
        filename,
        code
          .replace(getConstantsBody, newGetConstantsBody)
          .replace("#import", "#import <React/RCTUtils.h>\n#import")
      );
    });

  console.log("Files without constantsToExport: ");
  filesWithoutConstantsToExport.forEach((file) => console.log(file));
  console.log();

  console.log("Files with constantsToExport but no getConstants: ");
  filesWithConstantsToExportButNotGetConstants.forEach((file) =>
    console.log(file)
  );
  console.log();

  console.log("Files with requiresMainQueueSetup = NO: ");
  filesExplicitlyNotRequiringMainQueueSetup.forEach((file) =>
    console.log(file)
  );
}

if (!module.parent) {
  main();
}

```

Changelog: [Internal]

Reviewed By: fkgozali

Differential Revision: D21797048

fbshipit-source-id: a822a858fecdbe976e6197f8339e509dc7cd917f
  • Loading branch information
RSNara authored and facebook-github-bot committed Jun 3, 2020
1 parent e5bef73 commit 39d6773
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 35 deletions.
9 changes: 7 additions & 2 deletions React/CoreModules/RCTAppState.mm
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ - (dispatch_queue_t)methodQueue

- (facebook::react::ModuleConstants<JS::NativeAppState::Constants>)getConstants
{
return facebook::react::typedConstants<JS::NativeAppState::Constants>({
.initialAppState = RCTCurrentAppState(),
__block facebook::react::ModuleConstants<JS::NativeAppState::Constants> constants;
RCTUnsafeExecuteOnMainQueueSync(^{
constants = facebook::react::typedConstants<JS::NativeAppState::Constants>({
.initialAppState = RCTCurrentAppState(),
});
});

return constants;
}

#pragma mark - Lifecycle
Expand Down
21 changes: 13 additions & 8 deletions React/CoreModules/RCTDeviceInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,19 @@ static BOOL RCTIsIPhoneX()

- (NSDictionary<NSString *, id> *)getConstants
{
return @{
@"Dimensions" : RCTExportedDimensions(_bridge),
// Note:
// This prop is deprecated and will be removed in a future release.
// Please use this only for a quick and temporary solution.
// Use <SafeAreaView> instead.
@"isIPhoneX_deprecated" : @(RCTIsIPhoneX()),
};
__block NSDictionary<NSString *, id> *constants;
RCTUnsafeExecuteOnMainQueueSync(^{
constants = @{
@"Dimensions" : RCTExportedDimensions(self->_bridge),
// Note:
// This prop is deprecated and will be removed in a future release.
// Please use this only for a quick and temporary solution.
// Use <SafeAreaView> instead.
@"isIPhoneX_deprecated" : @(RCTIsIPhoneX()),
};
});

return constants;
}

- (void)didReceiveNewContentSizeMultiplier
Expand Down
35 changes: 20 additions & 15 deletions React/CoreModules/RCTPlatform.mm
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,27 @@ - (dispatch_queue_t)methodQueue

- (ModuleConstants<JS::NativePlatformConstantsIOS::Constants>)getConstants
{
UIDevice *device = [UIDevice currentDevice];
auto versions = RCTGetReactNativeVersion();
return typedConstants<JS::NativePlatformConstantsIOS::Constants>({
.forceTouchAvailable = RCTForceTouchAvailable() ? true : false,
.osVersion = [device systemVersion],
.systemName = [device systemName],
.interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]),
.isTesting = RCTRunningInTestEnvironment() ? true : false,
.reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder(
{.minor = [versions[@"minor"] doubleValue],
.major = [versions[@"major"] doubleValue],
.patch = [versions[@"patch"] doubleValue],
.prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]]
? folly::Optional<double>{}
: [versions[@"prerelease"] doubleValue]}),
__block ModuleConstants<JS::NativePlatformConstantsIOS::Constants> constants;
RCTUnsafeExecuteOnMainQueueSync(^{
UIDevice *device = [UIDevice currentDevice];
auto versions = RCTGetReactNativeVersion();
constants = typedConstants<JS::NativePlatformConstantsIOS::Constants>({
.forceTouchAvailable = RCTForceTouchAvailable() ? true : false,
.osVersion = [device systemVersion],
.systemName = [device systemName],
.interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]),
.isTesting = RCTRunningInTestEnvironment() ? true : false,
.reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder(
{.minor = [versions[@"minor"] doubleValue],
.major = [versions[@"major"] doubleValue],
.patch = [versions[@"patch"] doubleValue],
.prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]]
? folly::Optional<double>{}
: [versions[@"prerelease"] doubleValue]}),
});
});

return constants;
}

- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params
Expand Down
11 changes: 8 additions & 3 deletions React/CoreModules/RCTStatusBarManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,15 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification

- (facebook::react::ModuleConstants<JS::NativeStatusBarManagerIOS::Constants>)getConstants
{
return facebook::react::typedConstants<JS::NativeStatusBarManagerIOS::Constants>({
.HEIGHT = RCTSharedApplication().statusBarFrame.size.height,
.DEFAULT_BACKGROUND_COLOR = folly::none,
__block facebook::react::ModuleConstants<JS::NativeStatusBarManagerIOS::Constants> constants;
RCTUnsafeExecuteOnMainQueueSync(^{
constants = facebook::react::typedConstants<JS::NativeStatusBarManagerIOS::Constants>({
.HEIGHT = RCTSharedApplication().statusBarFrame.size.height,
.DEFAULT_BACKGROUND_COLOR = folly::none,
});
});

return constants;
}

- (facebook::react::ModuleConstants<JS::NativeStatusBarManagerIOS::Constants>)constantsToExport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "RCTSampleTurboModule.h"

#import <React/RCTUtils.h>
#import <UIKit/UIKit.h>

using namespace facebook::react;
Expand Down Expand Up @@ -45,14 +46,19 @@ - (void)invalidate

- (NSDictionary *)getConstants
{
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
__block NSDictionary *constants;
RCTUnsafeExecuteOnMainQueueSync(^{
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;

return @{
@"const1" : @YES,
@"const2" : @(screenSize.width),
@"const3" : @"something",
};
constants = @{
@"const1" : @YES,
@"const2" : @(screenSize.width),
@"const3" : @"something",
};
});

return constants;
}

// TODO: Remove once fully migrated to TurboModule.
Expand Down

0 comments on commit 39d6773

Please sign in to comment.