Skip to content
Open
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
20 changes: 20 additions & 0 deletions packages/react-native/Libraries/LogBox/Data/LogBoxData.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {Stack} from './LogBoxSymbolication';
import type {Category, ExtendedExceptionData, Message} from './parseLogBoxLog';

import DebuggerSessionObserver from '../../../src/private/devsupport/rndevtools/FuseboxSessionObserver';
import TracingStateObserver from '../../../src/private/devsupport/rndevtools/TracingStateObserver';
import toExtendedError from '../../../src/private/utilities/toExtendedError';
import parseErrorStack from '../../Core/Devtools/parseErrorStack';
import NativeLogBox from '../../NativeModules/specs/NativeLogBox';
Expand Down Expand Up @@ -71,6 +72,7 @@ let _isDisabled = false;
let _selectedIndex = -1;
let hasShownFuseboxWarningsMigrationMessage = false;
let hostTargetSessionObserverSubscription = null;
let tracingStateObserverSubscription = null;

let warningFilter: WarningFilter = function (format) {
return {
Expand Down Expand Up @@ -205,6 +207,20 @@ export function addLog(log: LogData): void {
);
}

if (tracingStateObserverSubscription == null) {
tracingStateObserverSubscription = TracingStateObserver.subscribe(
isTracing => {
if (isTracing) {
clear();
}
},
);
}

if (TracingStateObserver.isTracing()) {
return;
}

// If Host has Fusebox support
if (log.level === 'warn' && global.__FUSEBOX_HAS_FULL_CONSOLE_SUPPORT__) {
// And there is no active debugging session
Expand Down Expand Up @@ -241,6 +257,10 @@ export function addLog(log: LogData): void {
}

export function addException(error: ExtendedExceptionData): void {
if (TracingStateObserver.isTracing()) {
return;
}

// Parsing logs are expensive so we schedule this
// otherwise spammy logs would pause rendering.
setImmediate(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,4 +721,137 @@ describe('LogBoxData', () => {
LogBoxData.setAppInfo(() => info);
expect(LogBoxData.getAppInfo()).toBe(info);
});

describe('Performance tracing suppression', () => {
let mockIsTracing: () => boolean;
let mockSubscribeCallback: ((isTracing: boolean) => void) | null = null;

beforeEach(() => {
mockIsTracing = jest.fn(() => false);
mockSubscribeCallback = null;

jest.doMock(
'../../../../src/private/devsupport/rndevtools/TracingStateObserver',
() => ({
__esModule: true,
default: {
isTracing: () => mockIsTracing(),
subscribe: (callback: (isTracing: boolean) => void) => {
mockSubscribeCallback = callback;
return () => {
mockSubscribeCallback = null;
};
},
},
}),
);

jest.resetModules();
});

afterEach(() => {
jest.unmock(
'../../../../src/private/devsupport/rndevtools/TracingStateObserver',
);
});

it('suppresses logs when performance tracing is active', () => {
const LogBoxDataWithMock = require('../LogBoxData');

LogBoxDataWithMock.addLog({
level: 'warn',
message: {content: 'Before tracing', substitutions: []},
category: 'before-tracing',
componentStack: [],
});
jest.runOnlyPendingTimers();

const observerBefore = jest.fn();
LogBoxDataWithMock.observe(observerBefore).unsubscribe();
expect(Array.from(observerBefore.mock.calls[0][0].logs).length).toBe(1);

mockIsTracing = jest.fn(() => true);

LogBoxDataWithMock.addLog({
level: 'warn',
message: {content: 'During tracing', substitutions: []},
category: 'during-tracing',
componentStack: [],
});
jest.runOnlyPendingTimers();

const observerDuring = jest.fn();
LogBoxDataWithMock.observe(observerDuring).unsubscribe();
expect(Array.from(observerDuring.mock.calls[0][0].logs).length).toBe(1);
});

it('suppresses exceptions when performance tracing is active', () => {
const LogBoxDataWithMock = require('../LogBoxData');

LogBoxDataWithMock.addException({
message: 'Before tracing exception',
isComponentError: false,
originalMessage: 'Before tracing exception',
name: 'console.error',
componentStack: '',
stack: [],
id: 0,
isFatal: false,
});
jest.runOnlyPendingTimers();

const observerBefore = jest.fn();
LogBoxDataWithMock.observe(observerBefore).unsubscribe();
expect(Array.from(observerBefore.mock.calls[0][0].logs).length).toBe(1);

mockIsTracing = jest.fn(() => true);

LogBoxDataWithMock.addException({
message: 'During tracing exception',
isComponentError: false,
originalMessage: 'During tracing exception',
name: 'console.error',
componentStack: '',
stack: [],
id: 1,
isFatal: false,
});
jest.runOnlyPendingTimers();

const observerDuring = jest.fn();
LogBoxDataWithMock.observe(observerDuring).unsubscribe();
expect(Array.from(observerDuring.mock.calls[0][0].logs).length).toBe(1);
});

it('clears logs when tracing starts', () => {
const LogBoxDataWithMock = require('../LogBoxData');

LogBoxDataWithMock.addLog({
level: 'warn',
message: {content: 'Log 1', substitutions: []},
category: 'log-1',
componentStack: [],
});
LogBoxDataWithMock.addLog({
level: 'warn',
message: {content: 'Log 2', substitutions: []},
category: 'log-2',
componentStack: [],
});
jest.runOnlyPendingTimers();

const observerBefore = jest.fn();
LogBoxDataWithMock.observe(observerBefore).unsubscribe();
expect(Array.from(observerBefore.mock.calls[0][0].logs).length).toBe(2);

if (mockSubscribeCallback) {
mockSubscribeCallback(true);
}
jest.runOnlyPendingTimers();

const observerAfter = jest.fn();
LogBoxDataWithMock.observe(observerAfter).unsubscribe();
expect(Array.from(observerAfter.mock.calls[0][0].logs).length).toBe(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,15 @@ RuntimeTracingAgent::RuntimeTracingAgent(
if (state.enabledCategories.contains(tracing::Category::JavaScriptSampling)) {
targetController_.enableSamplingProfiler();
}
if (state.mode == tracing::Mode::CDP) {
targetController_.emitTracingStateChange(true);
}
}

RuntimeTracingAgent::~RuntimeTracingAgent() {
if (state_.mode == tracing::Mode::CDP) {
targetController_.emitTracingStateChange(false);
}
if (state_.enabledCategories.contains(
tracing::Category::JavaScriptSampling)) {
targetController_.disableSamplingProfiler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

#include <jsinspector-modern/RuntimeTarget.h>
#include <jsinspector-modern/RuntimeTargetGlobalStateObserver.h>
#include <jsinspector-modern/tracing/PerformanceTracer.h>
#include <jsinspector-modern/RuntimeTargetTracingStateObserver.h>

#include <utility>

Expand Down Expand Up @@ -43,6 +43,7 @@ void RuntimeTarget::installGlobals() {
// NOTE: RuntimeTarget::installDebuggerSessionObserver is in
// RuntimeTargetDebuggerSessionObserver.cpp
installDebuggerSessionObserver();
installTracingStateObserver();
// NOTE: RuntimeTarget::installNetworkReporterAPI is in
// RuntimeTargetNetwork.cpp
installNetworkReporterAPI();
Expand Down Expand Up @@ -157,6 +158,23 @@ void RuntimeTarget::emitDebuggerSessionDestroyed() {
});
}

void RuntimeTarget::installTracingStateObserver() {
jsExecutor_([](jsi::Runtime& runtime) {
jsinspector_modern::installTracingStateObserver(runtime);
});
}

void RuntimeTarget::emitTracingStateChange(bool isTracing) {
jsExecutor_([isTracing](jsi::Runtime& runtime) {
try {
emitTracingStateObserverChange(runtime, isTracing);
} catch (jsi::JSError&) {
// Suppress any errors, they should not be visible to the user
// and should not affect runtime.
}
});
}

void RuntimeTarget::enableSamplingProfiler() {
delegate_.enableSamplingProfiler();
}
Expand Down Expand Up @@ -275,6 +293,10 @@ RuntimeTargetController::collectSamplingProfile() {
return target_.collectSamplingProfile();
}

void RuntimeTargetController::emitTracingStateChange(bool isTracing) {
target_.emitTracingStateChange(isTracing);
}

void RuntimeTargetController::notifyDomainStateChanged(
Domain domain,
bool enabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <jsinspector-modern/tracing/TraceRecordingState.h>

#include <memory>
#include <optional>
#include <utility>

#ifndef JSINSPECTOR_EXPORT
Expand Down Expand Up @@ -147,6 +148,11 @@ class RuntimeTargetController {
*/
tracing::RuntimeSamplingProfile collectSamplingProfile();

/**
* Emits a tracing state change to JavaScript via the tracing state observer.
*/
void emitTracingStateChange(bool isTracing);

private:
RuntimeTarget &target_;
};
Expand Down Expand Up @@ -262,6 +268,7 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis<RuntimeTa
* session - HostTargetTraceRecording.
*/
std::weak_ptr<RuntimeTracingAgent> tracingAgent_;

/**
* Start sampling profiler for a particular JavaScript runtime.
*/
Expand Down Expand Up @@ -304,6 +311,13 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis<RuntimeTa
*/
void installDebuggerSessionObserver();

/**
* Installs __TRACING_STATE_OBSERVER__ object on the JavaScript's global
* object, which can be referenced from JavaScript side for determining the
* status of performance tracing.
*/
void installTracingStateObserver();

/**
* Installs the private __NETWORK_REPORTER__ object on the Runtime's
* global object.
Expand All @@ -322,6 +336,11 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis<RuntimeTa
*/
void emitDebuggerSessionDestroyed();

/**
* Emits a tracing state change to JavaScript via the tracing state observer.
*/
void emitTracingStateChange(bool isTracing);

/**
* \returns a globally unique ID for a network request.
* May be called from any thread as long as the RuntimeTarget is valid.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 "RuntimeTargetTracingStateObserver.h"

#include <jsinspector-modern/RuntimeTargetGlobalStateObserver.h>

namespace facebook::react::jsinspector_modern {

void installTracingStateObserver(jsi::Runtime& runtime) {
installGlobalStateObserver(
runtime,
"__TRACING_STATE_OBSERVER__",
"isTracing",
"onTracingStateChange");
}

void emitTracingStateObserverChange(jsi::Runtime& runtime, bool isTracing) {
emitGlobalStateObserverChange(
runtime, "__TRACING_STATE_OBSERVER__", "onTracingStateChange", isTracing);
}

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 <jsi/jsi.h>

namespace facebook::react::jsinspector_modern {

/**
* Installs __TRACING_STATE_OBSERVER__ object on the JavaScript's global
* object, which can be referenced from JavaScript side for determining the
* status of performance tracing.
*/
void installTracingStateObserver(jsi::Runtime &runtime);

/**
* Emits the tracing state change to JavaScript by calling onTracingStateChange
* on __TRACING_STATE_OBSERVER__.
*/
void emitTracingStateObserverChange(jsi::Runtime &runtime, bool isTracing);

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,28 @@
* This class provides a JS-friendly API over that global object.
*/
class GlobalStateObserver {
#hasNativeSupport: boolean;
#globalName: string;
#statusProperty: string;

constructor(globalName: string, statusProperty: string) {
this.#globalName = globalName;
this.#statusProperty = statusProperty;
this.#hasNativeSupport = global.hasOwnProperty(globalName);
}

#hasNativeSupport(): boolean {
return global.hasOwnProperty(this.#globalName);
}

getStatus(): boolean {
if (!this.#hasNativeSupport) {
if (!this.#hasNativeSupport()) {
return false;
}

return global[this.#globalName][this.#statusProperty];
}

subscribe(callback: (status: boolean) => void): () => void {
if (!this.#hasNativeSupport) {
if (!this.#hasNativeSupport()) {
return () => {};
}

Expand Down
Loading
Loading