A React Native wrapper for Mozilla's GeckoView browser engine. Android only.
GeckoView Version: 143.0.20250908174027
Build Requirements:
- Gradle: 8.9.1
- Android NDK: 27.0.12077973
- Android compileSdkVersion: 28+ (recommended: 34+)
- Android minSdkVersion: 16+ (GeckoView requires 21+)
- ✅ Supported on React Native 0.81+.
WhennewArchEnabled=true, thecom.facebook.reactGradle plugin runs codegen, buildslibreact_codegen_geckoviewspec.so, and registers the GeckoView ComponentDescriptor automatically. - 🔁 If the consuming app keeps
newArchEnabled=false, the library transparently falls back to the Paper renderer (no crashes or manual configuration required).
- In your app’s
android/gradle.propertiessetnewArchEnabled=true. - Clean & rebuild (
./gradlew app:installDebugoryarn android). - Verify the metro logs mention
Fabricand GeckoView renders normally.
If Fabric fails to initialize, the JS component prints a warning and falls back to Paper.
yarn add react-native-geckoviewAdd Mozilla's Maven repository to your android/build.gradle:
allprojects {
repositories {
// ... other repositories
maven {
url "https://maven.mozilla.org/maven2/"
}
}
}- ✅ Latest GeckoView 143 with modern web platform support
- ✅ Media playback with audio focus management
- ✅ Page load progress events and security monitoring
- ✅ Enhanced Tracking Protection (ETP)
- ✅ Cookie banner handling
- ✅ HTTP authentication prompts
- ✅ Popup and before-unload prompt handling
- ✅ JavaScript evaluation
- ✅ Session persistence and management
- ✅ Web extension support
- ✅ Navigation error handling
import React, { useRef } from 'react';
import GeckoView from 'react-native-geckoview';
function MyBrowser() {
const geckoRef = useRef(null);
return (
<GeckoView
ref={geckoRef}
source={{ uri: 'https://dart.art' }}
style={{ flex: 1 }}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
source |
{ uri: string } |
required | URL to load |
sessionKey |
string |
undefined |
Unique session identifier for persistence across remounts |
handleAudioFocus |
boolean |
false |
Automatically manage Android audio focus for media playback |
contentBlockingEnabled |
boolean |
true |
Enable content blocking features |
cookieBannerMode |
'reject' | 'accept' | 'none' |
'none' |
Cookie banner handling mode |
| Event | Payload | Description |
|---|---|---|
onGeckoPageStart |
{ url: string } |
Fired when page load starts |
onGeckoPageStop |
{ success: boolean } |
Fired when page load completes |
onGeckoProgressChange |
{ progress: number } |
Loading progress (0-100) |
onGeckoSecurityChange |
{ isSecure: boolean, host: string } |
SSL/security status change |
| Event | Payload | Description |
|---|---|---|
onPageError |
{ uri, errorCode, errorCategory, errorMessage } |
Navigation error occurred |
| Event | Payload | Description |
|---|---|---|
onMediaSessionAction |
{ action, title?, artist?, album? } |
Media playback state change |
Actions: 'play', 'pause', 'stop', 'activated', 'deactivated', 'metadata'
| Event | Payload | Description |
|---|---|---|
onGeckoAlert |
{ promptId, message, defaultValue? } |
JavaScript alert() |
onGeckoConfirm |
{ promptId, message, defaultValue? } |
JavaScript confirm() |
onGeckoPrompt |
{ promptId, message, defaultValue? } |
JavaScript prompt() |
onGeckoPopup |
{ promptId, targetUri?, type } |
Popup blocked |
onGeckoBeforeUnload |
{ promptId, type } |
Before page unload |
onGeckoAuth |
{ promptId, title?, message?, authOptions, type } |
HTTP authentication required |
| Event | Payload | Description |
|---|---|---|
onPermissionRequest |
{ requestId, permission, uri } |
Permission requested (camera, microphone, etc.) |
| Event | Payload | Description |
|---|---|---|
onWebExtensionMessage |
{ message } |
Message from web extension |
onTitleChange |
title: string |
Page title changed |
onFaviconChange |
uri: string |
Page favicon changed |
onContextMenu |
items, x, y |
Context menu requested |
onFocusExit |
`direction: 'up' | 'down' |
onFullscreenRequest |
enabled: boolean |
Fullscreen mode requested |
onPictureInPicture |
enabled: boolean |
Picture-in-picture mode requested |
Access methods via ref:
const geckoRef = useRef(null);
// Navigation
geckoRef.current?.reload();
geckoRef.current?.goBack();
geckoRef.current?.goForward();
geckoRef.current?.stop();
// JavaScript Execution
const result = await geckoRef.current?.evaluateJavaScript('document.title');
// Prompt Resolution
geckoRef.current?.resolvePrompt(promptId, true, 'text input');
// Permission Handling
geckoRef.current?.resolvePermission(requestId, true);
// Privacy Controls
geckoRef.current?.setEnhancedTrackingProtectionLevel(2); // 0=NONE, 1=DEFAULT, 2=STRICT
geckoRef.current?.setCookieBannerMode(1); // 0=DISABLED, 1=REJECT, 2=REJECT_OR_ACCEPT
// Web Extensions
geckoRef.current?.installWebExtension('asset://extensions/my-extension');
geckoRef.current?.sendWebExtensionMessage(JSON.stringify({ action: 'doSomething' }));
// Lifecycle
geckoRef.current?.shutdown();Persist browser sessions across component remounts:
function PersistentBrowser() {
const [url, setUrl] = useState('https://example.com');
return (
<GeckoView
source={{ uri: url }}
sessionKey="main-browser" // Preserves history and state
style={{ flex: 1 }}
/>
);
}function MediaBrowser() {
return (
<GeckoView
source={{ uri: 'https://youtube.com' }}
handleAudioFocus={true} // Auto-manage audio focus
onMediaSessionAction={(event) => {
const { action, title, artist } = event.nativeEvent;
console.log(`Media ${action}:`, { title, artist });
}}
style={{ flex: 1 }}
/>
);
}function BrowserWithProgress() {
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(0);
return (
<>
{loading && <ProgressBar progress={progress} />}
<GeckoView
source={{ uri: 'https://example.com' }}
onGeckoPageStart={() => {
setLoading(true);
setProgress(0);
}}
onGeckoProgressChange={(e) => {
setProgress(e.nativeEvent.progress);
}}
onGeckoPageStop={() => {
setLoading(false);
}}
style={{ flex: 1 }}
/>
</>
);
}function RobustBrowser() {
const [error, setError] = useState(null);
return (
<>
{error && <ErrorBanner message={error} />}
<GeckoView
source={{ uri: 'https://example.com' }}
onPageError={(event) => {
const { errorMessage, errorCategory } = event.nativeEvent;
setError(`${errorCategory}: ${errorMessage}`);
}}
style={{ flex: 1 }}
/>
</>
);
}function InteractiveBrowser() {
const geckoRef = useRef(null);
const [promptId, setPromptId] = useState(null);
const [authPrompt, setAuthPrompt] = useState(null);
const handlePopup = (event) => {
const { promptId, targetUri } = event.nativeEvent;
Alert.alert(
'Popup Blocked',
`Allow popup to ${targetUri}?`,
[
{
text: 'Block',
onPress: () => geckoRef.current?.resolvePrompt(promptId, false)
},
{
text: 'Allow',
onPress: () => geckoRef.current?.resolvePrompt(promptId, true)
}
]
);
};
const handleAuth = (event) => {
const { promptId, message } = event.nativeEvent;
setAuthPrompt({ promptId, message });
// Show custom auth dialog
};
const submitAuth = (username, password) => {
geckoRef.current?.resolvePrompt(
authPrompt.promptId,
true,
null,
{ username, password }
);
setAuthPrompt(null);
};
return (
<GeckoView
ref={geckoRef}
source={{ uri: 'https://example.com' }}
onGeckoPopup={handlePopup}
onGeckoAuth={handleAuth}
style={{ flex: 1 }}
/>
);
}function PrivacyBrowser() {
const geckoRef = useRef(null);
useEffect(() => {
// Set strict tracking protection
geckoRef.current?.setEnhancedTrackingProtectionLevel(2);
// Auto-reject cookie banners
geckoRef.current?.setCookieBannerMode(1);
}, []);
return (
<GeckoView
ref={geckoRef}
source={{ uri: 'https://example.com' }}
style={{ flex: 1 }}
/>
);
}function ScriptableBrowser() {
const geckoRef = useRef(null);
const getPageTitle = async () => {
const title = await geckoRef.current?.evaluateJavaScript('document.title');
console.log('Page title:', title);
};
const injectCSS = async () => {
await geckoRef.current?.evaluateJavaScript(`
const style = document.createElement('style');
style.textContent = 'body { background: #000 !important; }';
document.head.appendChild(style);
`);
};
return (
<GeckoView ref={geckoRef} source={{ uri: 'https://example.com' }} style={{ flex: 1 }} />
);
}function SplitBrowser() {
return (
<View style={{ flex: 1, flexDirection: 'row' }}>
<GeckoView
source={{ uri: 'https://mozilla.org' }}
sessionKey="left-pane"
style={{ flex: 1 }}
/>
<GeckoView
source={{ uri: 'https://example.com' }}
sessionKey="right-pane"
style={{ flex: 1 }}
/>
</View>
);
}Full TypeScript definitions are included. Import types:
import GeckoView, { GeckoViewProps, GeckoViewRef } from 'react-native-geckoview';
const MyComponent: React.FC = () => {
const geckoRef = useRef<GeckoViewRef>(null);
const props: GeckoViewProps = {
source: { uri: 'https://example.com' },
handleAudioFocus: true,
onGeckoPageStart: (event) => {
console.log('Started loading:', event.nativeEvent.url);
}
};
return <GeckoView ref={geckoRef} {...props} />;
};The library includes comprehensive test coverage (~78% overall):
- 249 JavaScript tests (~85% coverage) - Component, session, Fabric detection
- 191 Kotlin unit tests (~75% coverage) - All delegates and managers
- 13 E2E tests - Real device flows via Detox
Run tests:
cd example && npm test # JavaScript
cd example && npm run detox:test:android # E2E (requires emulator)
cd example/android && ./gradlew :react-native-geckoview:testDebugUnitTest # KotlinSee docs/TESTING.md for details.
For Web Extensions, custom prompts, and performance tips, see docs/ADVANCED.md.
MIT License - Copyright (c) 2025 Dart Technologies, Inc. and all contributors
Based on Mozilla's GeckoView. Ported from flutter-geckoview implementation.