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
11 changes: 9 additions & 2 deletions base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1784,9 +1784,16 @@ component("base") {
"profiler/module_cache_posix.cc",
"profiler/stack_copier_signal.cc",
"profiler/stack_copier_signal.h",
"profiler/thread_delegate_posix.cc",
"profiler/thread_delegate_posix.h",
]

if (!is_starboard) {
sources -= [
"profiler/thread_delegate_posix.cc",
"profiler/thread_delegate_posix.h",
]
}

sources -= [
# ../../base/process/process_metrics_posix.cc:119:27: error: invalid use of incomplete type 'mallinfo'
# struct mallinfo minfo = mallinfo();
"process/process_metrics_posix.cc",
Expand Down
1 change: 1 addition & 0 deletions base/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ static_library("test_support") {

if (is_starboard) {
sources += [
"test_file_util_linux.cc",
"test_support_starboard.cc",
"test_support_starboard.h",
]
Expand Down
95 changes: 78 additions & 17 deletions cobalt/tools/cdp_js_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
Usage from python:
import asyncio
from cobalt.tools.cdp_js_helper import evaluate
result = asyncio.run(evaluate("document.visibilityState", 9222))
result = asyncio.run(evaluate("document.visibilityState", "localhost", 9222))
"""

import argparse
Expand All @@ -32,28 +32,29 @@
import websockets


async def get_websocket_url(port):
async def get_websocket_url(host, port):
"""Retrieves the websocket debugger URL from the local Cobalt instance."""
try:
# Use a synchronous requests call for the JSON list, as it's simple.
response = requests.get(f'http://localhost:{port}/json', timeout=10)
response = requests.get(f'http://{host}:{port}/json', timeout=10)
targets = response.json()
for target in targets:
if target.get('type') == 'page' and target.get('webSocketDebuggerUrl'):
return target['webSocketDebuggerUrl']
except (RuntimeError, ValueError) as e:
print(f'Error connecting to devtools on port {port}: {e}', file=sys.stderr)
except (requests.exceptions.RequestException, ValueError) as e:
print(
f'Error connecting to devtools on {host}:{port}: {e}', file=sys.stderr)
return None


async def evaluate(expression, port):
"""Evaluates a JS expression via the Chrome DevTools Protocol."""
ws_url = await get_websocket_url(port)
async def evaluate(expression, host, port):
"""Evaluates a JavaScript expression via CDP."""
ws_url = await get_websocket_url(host, port)
if not ws_url:
return 'ERROR: No websocket URL'

try:
async with websockets.connect(ws_url) as websocket:
async with websockets.connect(ws_url, close_timeout=5) as websocket:
# Send a simple Runtime.evaluate message.
message = {
'id': 1,
Expand All @@ -63,24 +64,84 @@ async def evaluate(expression, port):
'returnByValue': True
}
}
await websocket.send(json.dumps(message))
response = await websocket.recv()
await asyncio.wait_for(websocket.send(json.dumps(message)), timeout=30)
response = await asyncio.wait_for(websocket.recv(), timeout=30)
result = json.loads(response)
if 'result' in result and 'result' in result['result']:
return result['result']['result'].get('value')
val = result['result']['result'].get('value')
# If the value is not a string (e.g. boolean), convert to string.
if val is True:
return 'True'
if val is False:
return 'False'
if isinstance(val, (dict, list)):
return json.dumps(val, separators=(',', ':'))
return str(val) if val is not None else 'None'
return f'ERROR: {response}'
except (RuntimeError, ValueError) as e:
print(f'CDP Error: {e}', file=sys.stderr)
return f'ERROR: {e}'
except (RuntimeError, ValueError, asyncio.TimeoutError, OSError) as e:
print(f'CDP Error: {type(e).__name__}: {e}', file=sys.stderr)
return f'ERROR: {type(e).__name__}: {e}'


async def wait_for_cdp(host, port, timeout, total_wait):
"""Waits for the CDP endpoint to become available."""
start_time = asyncio.get_event_loop().time()
while (asyncio.get_event_loop().time() - start_time) < total_wait:
Comment on lines +88 to +89
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using asyncio.get_event_loop().time() is generally fine within a running loop, but time.monotonic() is the standard and more robust way to measure elapsed time for timeouts in Python, as it is independent of the event loop state and clock adjustments.

try:
ws_url = await get_websocket_url(host, port)
if ws_url:
async with websockets.connect(ws_url, close_timeout=1) as websocket:
# Try to verify JS responsiveness, but don't fail if it times out
# (e.g. if the app is preloaded/suspended and JS is throttled).
try:
message = {
'id': 999,
'method': 'Runtime.evaluate',
'params': {
'expression': '1+1',
'returnByValue': True
}
}
await asyncio.wait_for(
websocket.send(json.dumps(message)), timeout=2)
await asyncio.wait_for(websocket.recv(), timeout=2)
return 'SUCCESS'
except asyncio.TimeoutError:
# If we connected but JS is non-responsive, it's still "ready" for
# connection.
return 'SUCCESS'
except (RuntimeError, ValueError, asyncio.TimeoutError, OSError):
pass
await asyncio.sleep(timeout)
return 'FAILURE'


def main():
parser = argparse.ArgumentParser()
parser.add_argument('--host', type=str, default='localhost')
parser.add_argument('--port', type=int, default=9222)
parser.add_argument('expression', type=str)
parser.add_argument(
'--wait', action='store_true', help='Wait for CDP to be ready')
parser.add_argument(
'--wait-timeout',
type=float,
default=1.0,
help='Interval between connection attempts')
parser.add_argument(
'--wait-total',
type=float,
default=30.0,
help='Total time to wait for CDP')
parser.add_argument('expression', type=str, nargs='?', default='')
args = parser.parse_args()

result = asyncio.run(evaluate(args.expression, args.port))
if args.wait:
result = asyncio.run(
wait_for_cdp(args.host, args.port, args.wait_timeout, args.wait_total))
else:
if not args.expression:
parser.error('expression is required when not using --wait')
result = asyncio.run(evaluate(args.expression, args.host, args.port))
print(result)


Expand Down
100 changes: 72 additions & 28 deletions cobalt/tools/test_lifecycle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
PORT=9223
LOG_FILE="lifecycle_run.log"
EXECUTABLE=${TEST_LIFECYCLE_EXECUTABLE:-"./out/linux-x64x11-modular_devel/cobalt_loader"}
HOST=${TEST_LIFECYCLE_HOST:-"localhost"}

# Ensure no previous test instances or Cobalt processes are running.
pgrep -f "[t]est_lifecycle.sh" > /tmp/test_lifecycle_pids.tmp || true
Expand All @@ -38,64 +39,107 @@ if [ -n "$OTHER_TEST_PIDS" ] || [ -n "$WAITER_PIDS" ] || [ -n "$COBALT_PIDS" ];
[ -n "$OTHER_TEST_PIDS" ] && echo "Found test_lifecycle.sh PIDs: $OTHER_TEST_PIDS"
[ -n "$WAITER_PIDS" ] && echo "Found wait_for_state.sh PIDs: $WAITER_PIDS"
[ -n "$COBALT_PIDS" ] && echo "Found cobalt_loader PIDs: $COBALT_PIDS"
echo "--- ps faux dump ---"
ps faux | grep -C 5 "test_lifecycle"
echo "--------------------"
echo "Current PID: $$"
echo "Parent PID: $PPID"
echo "BASHPID: $BASHPID"
echo "Please kill them before running this test."
exit 1
fi

echo "[TEST] Starting cobalt_loader with DevTools on port $PORT..."
echo "[TEST] Starting cobalt_loader with DevTools on $HOST:$PORT..."
rm -f $LOG_FILE
rm -rf ~/.cobalt_storage ~/.cobalt ~/.config/cobalt

$EXECUTABLE --remote-debugging-port=$PORT --no-sandbox > $LOG_FILE 2>&1 &
TEST_HTML="<html><body><h1>Test</h1><input autofocus></body></html>"
B64_HTML=$(echo "$TEST_HTML" | base64 -w 0)

$EXECUTABLE --url="data:text/html;base64,$B64_HTML" --remote-debugging-port=$PORT --no-sandbox > $LOG_FILE 2>&1 &
COBALT_PID=$!

echo "[TEST] Launched PID: $COBALT_PID. Waiting for DevTools..."
sleep 5
if ! vpython3 cobalt/tools/cdp_js_helper.py --host $HOST --port $PORT --wait --wait-total 60 | grep -q "SUCCESS"; then
echo "FAILURE: DevTools did not become ready in time."
kill -9 $COBALT_PID
exit 1
fi

echo "[TEST] Allowing time for app initialization to avoid V8 crashes..."
sleep 10

execute_js() {
vpython3 cobalt/tools/cdp_js_helper.py --host $HOST --port $PORT "$1"
}

echo "[TEST] Verifying initial state (Visible & Focused)..."
bash cobalt/tools/wait_for_state.sh "document.visibilityState" "visible" $PORT 120 || exit 1
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "True" $PORT 120 || exit 1
bash cobalt/tools/wait_for_state.sh "document.visibilityState" "visible" $PORT 120 $HOST || exit 1
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "True" $PORT 120 $HOST || exit 1

# Inject event logger now that we are in a stable initial state.
echo "[TEST] Injecting event logger..."
execute_js "window.event_log = [];
document.addEventListener('visibilitychange', () => {
window.event_log.push({type: 'visibilitychange', visibility: document.visibilityState});
});
window.addEventListener('focus', () => window.event_log.push({type: 'focus'}));
window.addEventListener('blur', () => window.event_log.push({type: 'blur'}));
document.addEventListener('freeze', () => window.event_log.push({type: 'freeze'}));
document.addEventListener('resume', () => window.event_log.push({type: 'resume'}));
window.focus();"

wait_and_pop_event() {
local expected=$1
echo "[WAIT] Waiting to pop event '$expected'..."
bash cobalt/tools/wait_for_state.sh "window.event_log.length > 0" "True" $PORT 10 $HOST || exit 1
local result=$(vpython3 cobalt/tools/cdp_js_helper.py --host $HOST --port $PORT "JSON.stringify(window.event_log.shift())" 2>/dev/null)
if [[ "$result" == *"$expected"* ]]; then
echo "[WAIT] SUCCESS: Popped expected event '$expected'"
else
echo "FAILURE: Expected '$expected', got '$result'"
exit 1
fi
}

echo "[TEST] Sending SIGWINCH (BLUR)..."
kill -SIGWINCH $COBALT_PID
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "False" $PORT || exit 1
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "False" $PORT 10 $HOST || exit 1
wait_and_pop_event '{"type":"blur"}'

echo "[TEST] Sending SIGCONT (FOCUS)..."
kill -SIGCONT $COBALT_PID
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "True" $PORT || exit 1
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "True" $PORT 10 $HOST || exit 1
wait_and_pop_event '{"type":"focus"}'

echo "[TEST] Sending SIGUSR1 (CONCEAL)..."
kill -SIGUSR1 $COBALT_PID
bash cobalt/tools/wait_for_state.sh "document.visibilityState" "hidden" $PORT || exit 1
# Concealing from focused state will blur then change visibility to hidden
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "False" $PORT 10 $HOST || exit 1
bash cobalt/tools/wait_for_state.sh "document.visibilityState" "hidden" $PORT 10 $HOST || exit 1
wait_and_pop_event '{"type":"blur"}'
wait_and_pop_event '{"type":"visibilitychange","visibility":"hidden"}'

echo "[TEST] Sending SIGTSTP (FREEZE)..."
kill -SIGTSTP $COBALT_PID

echo '[SLEEP] Wait 3 seconds, manually confirm that the window has disappeared.'
sleep 3
echo "[TEST] Sleeping to ensure app freezes..."
sleep 2

echo "[TEST] Sending SIGCONT (REVEAL & FOCUS)..."
echo "[TEST] Sending SIGCONT (RESUME & REVEAL & FOCUS)..."
kill -SIGCONT $COBALT_PID
bash cobalt/tools/wait_for_state.sh "document.visibilityState" "visible" $PORT 120 || exit 1
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "True" $PORT 120 || exit 1

echo '[SLEEP] Wait 3 seconds, manually confirm that the window has returned.'
sleep 3
echo "[TEST] Verifying freeze, resume, reveal, and focus events..."
bash cobalt/tools/wait_for_state.sh "document.visibilityState" "visible" $PORT 20 $HOST || exit 1
bash cobalt/tools/wait_for_state.sh "document.hasFocus()" "True" $PORT 20 $HOST || exit 1
wait_and_pop_event '{"type":"freeze"}'
wait_and_pop_event '{"type":"resume"}'
wait_and_pop_event '{"type":"focus"}'
wait_and_pop_event '{"type":"visibilitychange","visibility":"visible"}'

echo "[TEST] Sending SIGPWR (STOP)..."
kill -SIGPWR $COBALT_PID
sleep 5

if kill -0 $COBALT_PID 2>/dev/null; then
STAT=$(ps -p $COBALT_PID -o stat= | tr -d ' ')
if [[ "$STAT" != "Z"* ]]; then
echo "FAILURE: Cobalt (PID: $COBALT_PID) did not stop after SIGPWR. State: $STAT"
echo "[TEST] Cleaning up Cobalt (PID: $COBALT_PID)..."
kill -9 $COBALT_PID
exit 1
fi
echo "FAILURE: Cobalt (PID: $COBALT_PID) did not stop after SIGPWR."
echo "[TEST] Cleaning up Cobalt (PID: $COBALT_PID)..."
kill -9 $COBALT_PID
exit 1
fi

echo "[TEST] SUCCESS: Lifecycle transitions verified!"
Expand Down
22 changes: 12 additions & 10 deletions cobalt/tools/wait_for_state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Helper script to poll a JS expression via CDP until it matches an expected value.

EXPR=$1
EXPECTED=$2
PORT=$3
TIMEOUT=${4:-10}
PORT=${3:-9222}
TIMEOUT=${4:-30}
HOST=${5:-"localhost"}

START_TIME=$(date +%s)

echo "[WAIT] Waiting for '$EXPR' to be '$EXPECTED' (timeout: ${TIMEOUT}s)..."
echo "[WAIT] Waiting for '$EXPR' to be '$EXPECTED' on $HOST:$PORT (timeout: ${TIMEOUT}s)..."

while true; do
RESULT=$(vpython3 cobalt/tools/cdp_js_helper.py --port $PORT "$EXPR" 2>/dev/null)
if [ "$RESULT" == "$EXPECTED" ]; then
RESULT=$(vpython3 cobalt/tools/cdp_js_helper.py --host $HOST --port $PORT "$EXPR" 2>/dev/null)
if [[ "$RESULT" == *"$EXPECTED"* ]]; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The transition from an exact match to a substring match ([[ "$RESULT" == *"$EXPECTED"* ]]) is risky and can lead to false positives. For example, if the expected state is visible, this condition would incorrectly pass if the result is an error message containing that word (e.g., ERROR: visible state not reached). Since cdp_js_helper.py now returns clean string representations of the values, an exact match is safer and more reliable.

Suggested change
if [[ "$RESULT" == *"$EXPECTED"* ]]; then
if [ "$RESULT" == "$EXPECTED" ]; then

END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "[WAIT] SUCCESS: '$EXPR' is now '$EXPECTED' (took ${DURATION}s)"
echo "[WAIT] SUCCESS: '$EXPR' matched '$EXPECTED' (took ${DURATION}s)"
exit 0
fi

CURRENT_TIME=$(date +%s)
if [ $((CURRENT_TIME - START_TIME)) -gt $TIMEOUT ]; then
echo "FAILURE: Timeout waiting for $EXPR to be $EXPECTED. Got: $RESULT"
ELAPSED=$((CURRENT_TIME - START_TIME))
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "FAILURE: Timeout waiting for '$EXPR' to be '$EXPECTED'. Last result: '$RESULT'"
exit 1
fi
sleep 0.5
sleep 1
done
6 changes: 3 additions & 3 deletions components/crash/core/app/crashpad.cc
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,12 @@ void DumpWithoutCrashAndDeferProcessingAtPath(const base::FilePath& path) {

#endif

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)) && !BUILDFLAG(IS_STARBOARD)
void CrashWithoutDumping(const std::string& message) {
crashpad::CrashpadClient::CrashWithoutDump(message);
}
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
// BUILDFLAG(IS_ANDROID)
#endif // (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
// BUILDFLAG(IS_ANDROID)) && !BUILDFLAG(IS_STARBOARD)

void GetReports(std::vector<Report>* reports) {
#if BUILDFLAG(IS_WIN)
Expand Down
2 changes: 1 addition & 1 deletion media/gpu/sandbox/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ source_set("sandbox") {
"hardware_video_decoding_sandbox_hook_linux.h",
]
}
if (is_linux || is_chromeos) {
if ((is_linux && !is_starboard) || is_chromeos) {
sources += [
"hardware_video_encoding_sandbox_hook_linux.cc",
"hardware_video_encoding_sandbox_hook_linux.h",
Expand Down
2 changes: 2 additions & 0 deletions sandbox/linux/suid/client/setuid_sandbox_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ SetuidSandboxClient::SetuidSandboxClient(std::unique_ptr<base::Environment> env)
DCHECK(env_);
}

#if !BUILDFLAG(IS_STARBOARD)
SetuidSandboxClient::~SetuidSandboxClient() = default;
#endif

void SetuidSandboxClient::CloseDummyFile() {
// When we're launched through the setuid sandbox, SetupLaunchOptions
Expand Down
Loading
Loading