Techniques
Technical Implementations & Hacks
This document outlines the specific techniques and aggressive hacks used to modify the Facebook DOM and bypass Facebook’s anti-tampering measures.
1. Shadow DOM Event Isolation
Problem: Facebook’s React 18 event system attaches a global listener to document.documentElement that strictly requires all DOM nodes to have internal __reactFiber$ properties. Injecting raw HTML (like the Settings UI) into the body caused fatal white-screen crashes whenever the user clicked our injected UI.
Solution: The custom Settings UI is entirely encapsulated within a closed Shadow DOM (element.attachShadow({mode: 'closed'})). This acts as an event silo. Clicks inside the Shadow DOM bubble up to the Shadow Host, where stopPropagation() is called, completely hiding the events from React’s global listener while allowing our UI to function.
2. Phase Capture Event Interception (Links)
Problem: Facebook intercepts clicks on external links (like YouTube or news articles) and forces them through their own internal React router or tracking redirect (l.php), trapping the user inside the WebView.
Solution: A global click listener is attached to document using the capture phase (true). This allows Nobook to see the click before React can process it. If it’s an external link, we call e.preventDefault() and e.stopPropagation(), immediately killing Facebook’s logic, and we dispatch the raw URL to Rust via IPC to be opened natively.
3. Robust Element Nuking (Suggested & PYMK)
Problem: Facebook constantly changes DOM structures and obfuscates class names to prevent adblockers. Standard CSS selectors break frequently.
Solution: Instead of targeting classes, we use aggressive MutationObserver loops to scan the raw text of elements (innerText.replace(...)). When target text like “Suggested for you” is found, we walk up the tree using closest('article') or fallback DOM ancestry to find the main container. We then forcefully apply display: none !important, height: 0.1px, and opacity: 0 to destroy it entirely.
4. Rust Native Hook Fallback (on_navigation)
Problem: Sometimes Facebook forces a redirect using window.location.href = ..., which bypasses the JS click interceptor.
Solution: We use Tauri’s native on_navigation hook in lib.rs to intercept webview navigation at the OS level. If the WebView attempts to navigate to a non-Facebook host, we return false (blocking the navigation) and spawn the system default browser using rundll32 url.dll,FileProtocolHandler.
5. Lazy Hot-Reload Helper
Problem: We need the UI to update immediately when settings change, but polling the file system from JS is messy, and setting up a full Rust file watcher crate (like notify) is overkill and adds bloat.
Solution: The “Ponytail” approach. A single std::thread loop in Rust sleeps for 2 seconds and manually checks the modified() timestamp of .nobook_settings.json. If it changes, it emits a Tauri event to trigger a hot reload, but only if the Settings UI is closed (preventing invasive reloads while the user is actively modifying settings).
6. IPC File Logging
Problem: Debugging injected scripts in a production WebView is impossible without opening DevTools.
Solution: The JS payload overrides console.log, warn, error, and info directly in the WebView window context. Every log message and unhandled exception is stringified and sent to Rust via IPC, which blindly appends it to a nobook.log file in the user’s AppData folder.