Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.content.Context
import android.content.Intent
import android.nfc.NfcAdapter
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.graphics.createBitmap
import com.facebook.common.logging.FLog
import com.facebook.infer.annotation.Assertions
Expand Down Expand Up @@ -448,6 +449,19 @@ public class ReactHostImpl(
InspectorNetworkHelper.loadNetworkResource(url, listener)
}

@DoNotStrip
private fun setEmulatedMedia(colorScheme: String) {
UiThreadUtil.runOnUiThread {
val mode =
when (colorScheme) {
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
"light" -> AppCompatDelegate.MODE_NIGHT_NO
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
AppCompatDelegate.setDefaultNightMode(mode)
}
}

@DoNotStrip
private fun captureScreenshot(format: String, quality: Int): String? {
val activity = currentActivity ?: return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ std::optional<std::string> JReactHostInspectorTarget::captureScreenshot(
return std::nullopt;
}

bool JReactHostInspectorTarget::onSetEmulatedMedia(
const jsinspector_modern::HostTargetDelegate::SetEmulatedMediaRequest&
request) {
if (auto javaReactHostImplStrong = javaReactHostImpl_->get()) {
javaReactHostImplStrong->setEmulatedMedia(request.colorScheme);
return true;
}
return false;
}

HostTarget* JReactHostInspectorTarget::getInspectorTarget() {
return inspectorTarget_ ? inspectorTarget_.get() : nullptr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ struct JReactHostImpl : public jni::JavaClass<JReactHostImpl> {
"captureScreenshot");
return method(self(), jni::make_jstring(format), static_cast<jint>(quality));
}

void setEmulatedMedia(const std::string &colorScheme)
{
static auto method = javaClassStatic()->getMethod<void(jni::local_ref<jni::JString>)>("setEmulatedMedia");
method(self(), jni::make_jstring(colorScheme));
}
};

/**
Expand Down Expand Up @@ -284,6 +290,7 @@ class JReactHostInspectorTarget : public jni::HybridClass<JReactHostInspectorTar
jsinspector_modern::ScopedExecutor<jsinspector_modern::NetworkRequestListener> executor) override;
std::optional<std::string> captureScreenshot(
const jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest &request) override;
bool onSetEmulatedMedia(const jsinspector_modern::HostTargetDelegate::SetEmulatedMediaRequest &request) override;
jsinspector_modern::HostTargetTracingDelegate *getTracingDelegate() override;

private:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "EmulationAgent.h"

#include <jsinspector-modern/cdp/CdpJson.h>

namespace facebook::react::jsinspector_modern {

EmulationAgent::EmulationAgent(
FrontendChannel frontendChannel,
HostTargetController& hostTargetController)
: frontendChannel_(std::move(frontendChannel)),
hostTargetController_(hostTargetController) {}

bool EmulationAgent::handleRequest(const cdp::PreparsedRequest& req) {
if (req.method == "Emulation.setEmulatedMedia") {
handleSetEmulatedMedia(req);
return true;
}

return false;
}

void EmulationAgent::handleSetEmulatedMedia(const cdp::PreparsedRequest& req) {
if (req.params.isObject() && req.params.count("media") != 0u &&
!req.params.at("media").empty()) {
frontendChannel_(
cdp::jsonError(
req.id,
cdp::ErrorCode::MethodNotFound,
"Emulation.setEmulatedMedia: media type emulation is not supported"));
return;
}

if (!req.params.isObject() || req.params.count("features") == 0u ||
!req.params.at("features").isArray()) {
frontendChannel_(cdp::jsonResult(req.id));
return;
}

const auto& features = req.params.at("features");

std::string colorSchemeValue;
bool hasColorScheme = false;

for (const auto& feature : features) {
if (!feature.isObject() || feature.count("name") == 0u) {
continue;
}

const auto& name = feature.at("name").asString();
const auto value =
feature.count("value") != 0u ? feature.at("value").asString() : "";

if (name == "prefers-color-scheme") {
hasColorScheme = true;
colorSchemeValue = value;
continue;
}

// Unsupported features are OK if their value is empty (reset).
// DevTools sends all features on every update, with empty values for
// features that aren't being emulated.
if (!value.empty()) {
frontendChannel_(
cdp::jsonError(
req.id,
cdp::ErrorCode::MethodNotFound,
"Emulation.setEmulatedMedia: unsupported media feature '" + name +
"'"));
return;
}
}

if (hasColorScheme && !colorSchemeValue.empty() &&
colorSchemeValue != "light" && colorSchemeValue != "dark") {
frontendChannel_(
cdp::jsonError(
req.id,
cdp::ErrorCode::InvalidParams,
"Emulation.setEmulatedMedia: invalid value '" + colorSchemeValue +
"' for prefers-color-scheme (expected 'light', 'dark', or '')"));
return;
}

if (hasColorScheme) {
bool success = hostTargetController_.getDelegate().onSetEmulatedMedia(
{.colorScheme = colorSchemeValue});

if (!success) {
frontendChannel_(
cdp::jsonError(
req.id,
cdp::ErrorCode::InternalError,
"Emulation.setEmulatedMedia: failed to apply color scheme override"));
return;
}
}

frontendChannel_(cdp::jsonResult(req.id));
}

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include "HostTarget.h"
#include "InspectorInterfaces.h"

#include <jsinspector-modern/cdp/CdpJson.h>

namespace facebook::react::jsinspector_modern {

/**
* Provides an agent for handling CDP's Emulation domain.
* Currently supports Emulation.setEmulatedMedia (prefers-color-scheme only).
*/
class EmulationAgent {
public:
/**
* \param frontendChannel A channel used to send responses to the
* frontend.
* \param hostTargetController An interface to the HostTarget that this agent
* is attached to. The caller is responsible for ensuring that the
* HostTargetDelegate and underlying HostTarget both outlive the agent.
*/
EmulationAgent(FrontendChannel frontendChannel, HostTargetController &hostTargetController);

/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously.
* \param req The parsed request.
* \returns true if the request was handled.
*/
bool handleRequest(const cdp::PreparsedRequest &req);

private:
void handleSetEmulatedMedia(const cdp::PreparsedRequest &req);

FrontendChannel frontendChannel_;
HostTargetController &hostTargetController_;
};

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "InstanceAgent.h"

#ifdef REACT_NATIVE_DEBUGGER_ENABLED
#include "EmulationAgent.h"
#include "InspectorFlags.h"
#include "InspectorInterfaces.h"
#include "NetworkIOAgent.h"
Expand Down Expand Up @@ -50,7 +51,8 @@ class HostAgent::Impl final {
sessionState_(sessionState),
networkIOAgent_(NetworkIOAgent(frontendChannel, std::move(executor))),
tracingAgent_(
TracingAgent(frontendChannel, sessionState, targetController)) {}
TracingAgent(frontendChannel, sessionState, targetController)),
emulationAgent_(EmulationAgent(frontendChannel, targetController)) {}

~Impl() {
if (isPausedInDebuggerOverlayVisible_) {
Expand Down Expand Up @@ -380,6 +382,11 @@ class HostAgent::Impl final {
return;
}

if (!requestState.isFinishedHandlingRequest &&
emulationAgent_.handleRequest(req)) {
return;
}

if (!requestState.isFinishedHandlingRequest && instanceAgent_ &&
instanceAgent_->handleRequest(req)) {
return;
Expand Down Expand Up @@ -509,6 +516,8 @@ class HostAgent::Impl final {
NetworkIOAgent networkIOAgent_;

TracingAgent tracingAgent_;

EmulationAgent emulationAgent_;
};

#else
Expand Down
25 changes: 25 additions & 0 deletions packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
std::optional<int> quality;
};

struct SetEmulatedMediaRequest {
/**
* The color scheme to emulate: "light", "dark", or "" (reset to system
* default).
*/
std::string colorScheme;

inline bool operator==(const SetEmulatedMediaRequest &rhs) const
{
return colorScheme == rhs.colorScheme;
}
};

virtual ~HostTargetDelegate() override;

/**
Expand Down Expand Up @@ -206,6 +219,18 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
return std::nullopt;
}

/**
* Called when the debugger requests an emulated media override via
* @cdp Emulation.setEmulatedMedia. Currently only supports the
* prefers-color-scheme media feature.
*
* \returns true if the override was applied successfully.
*/
virtual bool onSetEmulatedMedia(const SetEmulatedMediaRequest & /*request*/)
{
return false;
}

/**
* An optional delegate that will be used by HostTarget to notify about tracing-related events.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,43 @@ void loadNetworkResource(const RCTInspectorLoadNetworkResourceRequest &params, R
[networkHelper_ loadNetworkResourceWithParams:params executor:executor];
}

#if TARGET_OS_IPHONE
bool onSetEmulatedMedia(const SetEmulatedMediaRequest &request) override
{
RCTAssertMainQueue();
UIWindow *keyWindow = nil;
for (UIScene *scene in RCTSharedApplication().connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive &&
[scene isKindOfClass:[UIWindowScene class]]) {
auto *windowScene = (UIWindowScene *)scene;
for (UIWindow *win in windowScene.windows) {
if (win.isKeyWindow) {
keyWindow = win;
break;
}
}
}
if (keyWindow != nil) {
break;
}
}

if (keyWindow == nil) {
return false;
}

UIUserInterfaceStyle style = UIUserInterfaceStyleUnspecified;
if (request.colorScheme == "dark") {
style = UIUserInterfaceStyleDark;
} else if (request.colorScheme == "light") {
style = UIUserInterfaceStyleLight;
}

keyWindow.overrideUserInterfaceStyle = style;
return true;
}
#endif

#if TARGET_OS_IPHONE && defined(REACT_NATIVE_DEBUGGER_ENABLED)
std::optional<std::string> captureScreenshot(const PageCaptureScreenshotRequest &request) override
{
Expand Down
13 changes: 13 additions & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -2821,6 +2821,7 @@ class facebook::react::JReactHostInspectorTarget : public jni::HybridClass<faceb
public static constexpr auto kJavaDescriptor;
public static jni::local_ref<JReactHostInspectorTarget::jhybriddata> initHybrid(jni::alias_ref<JReactHostInspectorTarget::jhybridobject> jobj, jni::alias_ref<facebook::react::JReactHostImpl> reactHost, jni::alias_ref<JExecutor::javaobject> javaExecutor);
public static void registerNatives();
public virtual bool onSetEmulatedMedia(const facebook::react::jsinspector_modern::HostTargetDelegate::SetEmulatedMediaRequest&) override;
public virtual facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() override;
public virtual facebook::react::jsinspector_modern::HostTargetTracingDelegate* getTracingDelegate() override;
public virtual std::optional<std::string> captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&) override;
Expand Down Expand Up @@ -7278,6 +7279,7 @@ struct facebook::react::JReactHostImpl : public facebook::jni::JavaClass<faceboo
public jni::local_ref<jni::JString> captureScreenshot(const std::string& format, int quality) const;
public static constexpr auto kJavaDescriptor;
public void loadNetworkResource(const std::string& url, jni::local_ref<InspectorNetworkRequestListener::javaobject> listener) const;
public void setEmulatedMedia(const std::string& colorScheme);
public void setPausedInDebuggerMessage(std::optional<std::string> message);
}

Expand Down Expand Up @@ -10320,6 +10322,11 @@ class facebook::react::jsinspector_modern::ConsoleTaskOrchestrator {
public ~ConsoleTaskOrchestrator() = default;
}

class facebook::react::jsinspector_modern::EmulationAgent {
public EmulationAgent(facebook::react::jsinspector_modern::FrontendChannel frontendChannel, facebook::react::jsinspector_modern::HostTargetController& hostTargetController);
public bool handleRequest(const facebook::react::jsinspector_modern::cdp::PreparsedRequest& req);
}

class facebook::react::jsinspector_modern::ExecutionContextManager {
public int32_t allocateExecutionContextId();
}
Expand Down Expand Up @@ -10417,6 +10424,7 @@ class facebook::react::jsinspector_modern::HostTargetDelegate : public facebook:
public HostTargetDelegate(facebook::react::jsinspector_modern::HostTargetDelegate&&) = delete;
public facebook::react::jsinspector_modern::HostTargetDelegate& operator=(const facebook::react::jsinspector_modern::HostTargetDelegate&) = delete;
public facebook::react::jsinspector_modern::HostTargetDelegate& operator=(facebook::react::jsinspector_modern::HostTargetDelegate&&) = delete;
public virtual bool onSetEmulatedMedia(const facebook::react::jsinspector_modern::HostTargetDelegate::SetEmulatedMediaRequest&);
public virtual facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() = 0;
public virtual facebook::react::jsinspector_modern::HostTargetTracingDelegate* getTracingDelegate();
public virtual std::optional<std::string> captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&);
Expand All @@ -10442,6 +10450,11 @@ struct facebook::react::jsinspector_modern::HostTargetDelegate::PageReloadReques
public std::optional<std::string> scriptToEvaluateOnLoad;
}

struct facebook::react::jsinspector_modern::HostTargetDelegate::SetEmulatedMediaRequest {
public bool operator==(const facebook::react::jsinspector_modern::HostTargetDelegate::SetEmulatedMediaRequest& rhs) const;
public std::string colorScheme;
}

class facebook::react::jsinspector_modern::HostTargetTraceRecording {
public HostTargetTraceRecording(facebook::react::jsinspector_modern::HostTarget& hostTarget, facebook::react::jsinspector_modern::tracing::Mode tracingMode, std::set<facebook::react::jsinspector_modern::tracing::Category> enabledCategories, std::optional<facebook::react::HighResDuration> windowSize = std::nullopt);
public bool isBackgroundInitiated() const;
Expand Down
Loading
Loading