|
| 1 | +# Debugging Web Inspector |
| 2 | + |
| 3 | +This guide covers techniques for debugging the Web Inspector frontend itself -- the JavaScript application that provides WebKit's developer tools UI. It is organized around problems you will encounter when developing and testing Web Inspector. |
| 4 | + |
| 5 | +Throughout this guide, "engineering build" means a Debug or Release build where `COMBINE_INSPECTOR_RESOURCES=NO`. The `Debug/Bootstrap.js` file is included and sets `WI.isEngineeringBuild = true`, which enables Engineering and Debug settings panes, the Debug UI toggle, and the "inspect inspector" button. Production builds combine all JS/CSS into `Main.js`/`Main.css` and strip the `Debug/` directory entirely. |
| 6 | + |
| 7 | +## "My change isn't showing up" |
| 8 | + |
| 9 | +You edited a JavaScript or CSS file under `Source/WebInspectorUI/UserInterface/`, but Web Inspector still shows the old behavior. |
| 10 | + |
| 11 | +**Root cause:** Inspector frontend files are copied from the source tree into the framework bundle during the build. Editing a source file does not update the running copy. |
| 12 | + |
| 13 | +**Fast JS-only rebuild:** |
| 14 | + |
| 15 | +```bash |
| 16 | +make -C Source/WebInspectorUI/ |
| 17 | +``` |
| 18 | + |
| 19 | +This invokes `copy-user-interface-resources.pl` to re-copy files. It completes nearly instantly because no compilation is involved -- just file copies via `ditto`. This is dramatically faster than a full `make GROUP=webkit debug` (~60 seconds). |
| 20 | + |
| 21 | +After rebuilding, reload the Inspector to pick up changes: |
| 22 | + |
| 23 | +- Press **Option + Shift + Command + R** (calls `InspectorFrontendHost.reopen()`) |
| 24 | +- Or right-click in the Inspector (with Debug UI enabled) and choose **"Reload Web Inspector"** |
| 25 | + |
| 26 | +**When you need a full rebuild:** If you changed C++ code in `Source/WebKit/`, `Source/WebCore/inspector/`, or `Source/JavaScriptCore/inspector/`, use `make GROUP=webkit debug`. |
| 27 | + |
| 28 | +## "I need to see protocol messages" |
| 29 | + |
| 30 | +You suspect protocol commands are not being sent, events are not arriving, or parameters are wrong. |
| 31 | + |
| 32 | +### Option 1: Debug UI toggle (engineering builds) |
| 33 | + |
| 34 | +1. Press **Option + Shift + Command + D** to reveal the Debug UI. |
| 35 | +2. Click the **console icon button** in the tab bar: |
| 36 | + - **Click** = filtered logging (hides `(multi)` target messages). |
| 37 | + - **Shift-click** = unfiltered logging (shows everything). Button turns **purple**. |
| 38 | + - **Click again** = off. |
| 39 | + |
| 40 | +Messages appear in the Inspector console. To view them, open Inspector^2 (see below). |
| 41 | + |
| 42 | +### Option 2: Settings tab (engineering builds) |
| 43 | + |
| 44 | +Open Settings (gear icon). Under **Debug > Protocol Logging:** |
| 45 | + |
| 46 | +| Setting | Effect | |
| 47 | +|---------|--------| |
| 48 | +| **Messages** | Logs all protocol messages (`InspectorBackend.dumpInspectorProtocolMessages`) | |
| 49 | +| **Time Stats** | Adds RTT and dispatch timing per message | |
| 50 | +| **Log as Text** | Forces JSON strings instead of expandable objects | |
| 51 | + |
| 52 | +### Option 3: Programmatic flags |
| 53 | + |
| 54 | +```javascript |
| 55 | +InspectorBackend.dumpInspectorProtocolMessages = true; |
| 56 | +InspectorBackend.filterMultiplexingBackendInspectorProtocolMessages = false; |
| 57 | +InspectorBackend.dumpInspectorTimeStats = true; |
| 58 | +``` |
| 59 | + |
| 60 | +Each logged message is formatted by `LoggingProtocolTracer` as: |
| 61 | + |
| 62 | +``` |
| 63 | +request (page-1): {"id":42,"method":"DOM.getDocument","params":{}} |
| 64 | +response (page-1): {"id":42,"result":{"root":{...}}} |
| 65 | +event (page-1): {"method":"DOM.documentUpdated","params":{}} |
| 66 | +``` |
| 67 | + |
| 68 | +Target identifiers: `(multi)` = multiplexing backend, `(page-N)` = page target, `(frame-N)` = frame target (Site Isolation), `(worker-N)` = worker target. |
| 69 | + |
| 70 | +### Option 4: Capture a protocol trace |
| 71 | + |
| 72 | +In engineering builds with Debug UI enabled, right-click > **Protocol Debugging > Capture Trace**. Perform actions, then right-click > **Export Trace...** to save as JSON. |
| 73 | + |
| 74 | +## "I need to set a breakpoint in Inspector JS code" |
| 75 | + |
| 76 | +Web Inspector is itself a web page rendered by a WKWebView. You can open a second instance to debug the first ("Inspector^2"). |
| 77 | + |
| 78 | +### Method 1: Debug UI button (engineering builds) |
| 79 | + |
| 80 | +1. Open Web Inspector on any page. |
| 81 | +2. Press **Option + Shift + Command + D** to enable Debug UI. |
| 82 | +3. Click the **numbered button** in the tab bar (shows "2"). A new Inspector window opens. |
| 83 | + |
| 84 | +Under the hood, the button calls `InspectorFrontendHost.inspectInspector()`, which enables `developerExtrasEnabled` on the Inspector's page and opens a new inspector controller. |
| 85 | + |
| 86 | +### Method 2: Settings (all builds) |
| 87 | + |
| 88 | +Open Settings > **Experimental** > check **"Allow Inspecting Web Inspector"**. In engineering builds, the numbered button appears in Debug UI. In non-engineering builds, use Safari's **Develop** menu -- the Inspector's WebContent process appears as an inspectable target. |
| 89 | + |
| 90 | +### Method 3: defaults write (release builds) |
| 91 | + |
| 92 | +```bash |
| 93 | +defaults write com.apple.Safari WebKitDebugWebInspectorEngineeringSettingsAllowed -bool true |
| 94 | +``` |
| 95 | + |
| 96 | +### Inspection levels |
| 97 | + |
| 98 | +Each nested inspector has an `inspectionLevel` property (via `InspectorFrontendHost.inspectionLevel`). Level 1 is the normal Inspector; level 2 is Inspector^2. Each level gets its own preferences namespace, so settings do not collide. |
| 99 | + |
| 100 | +## "My test is timing out" |
| 101 | + |
| 102 | +Inspector layout tests are asynchronous. Common timeout causes: waiting for events that never fire (wrong event name or wrong class), unresolved promises, or silently swallowed exceptions. |
| 103 | + |
| 104 | +### Step 1: Enable debug logging |
| 105 | + |
| 106 | +Call `InspectorTest.debug()`: |
| 107 | + |
| 108 | +```javascript |
| 109 | +function test() |
| 110 | +{ |
| 111 | + InspectorTest.debug(); |
| 112 | + // ... rest of test |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +This enables both `dumpActivityToSystemConsole` (tees output to stderr) and `dumpInspectorProtocolMessages` (logs all protocol traffic). |
| 117 | + |
| 118 | +For maximum visibility, set all flags individually: |
| 119 | + |
| 120 | +```javascript |
| 121 | +InspectorTest.forceDebugLogging = true; |
| 122 | +InspectorTest.dumpActivityToSystemConsole = true; |
| 123 | +InspectorBackend.dumpInspectorProtocolMessages = true; |
| 124 | +InspectorBackend.filterMultiplexingBackendInspectorProtocolMessages = false; |
| 125 | +``` |
| 126 | + |
| 127 | +| Flag | Effect | |
| 128 | +|------|--------| |
| 129 | +| `forceDebugLogging` | Routes `log()` through `debugLog()` (synchronous stderr). Survives timeouts. | |
| 130 | +| `dumpActivityToSystemConsole` | Tees `addResult`, `completeTest` calls to stderr | |
| 131 | +| `dumpInspectorProtocolMessages` | Logs every protocol message to stderr | |
| 132 | +| `filterMultiplexingBackendInspectorProtocolMessages` | When `false`, includes `(multi)` messages | |
| 133 | + |
| 134 | +For **protocol tests**: use `ProtocolTest.debug()` or the equivalent `ProtocolTest.*` flags. |
| 135 | + |
| 136 | +### Step 2: Verify event names exist |
| 137 | + |
| 138 | +```bash |
| 139 | +grep -r "WI.Frame.Event.ExecutionContextChanged" Source/WebInspectorUI/ |
| 140 | +``` |
| 141 | + |
| 142 | +If zero results, the event does not exist on that class. Check adjacent classes -- naming is inconsistent (e.g., `WI.Frame.Event` vs. `WI.FrameTarget.Event` vs. `WI.Target.Event`). |
| 143 | + |
| 144 | +## "I can't read the stack traces" |
| 145 | + |
| 146 | +Stack frames reference `Main.js` at high line numbers, or all Inspector code appears in a single file. |
| 147 | + |
| 148 | +**Root cause:** The build combined all JavaScript into `Main.js`. Controlled by: |
| 149 | + |
| 150 | +| Setting | Base.xcconfig (Production) | DebugRelease.xcconfig (Engineering) | |
| 151 | +|---------|---------------------------|-------------------------------------| |
| 152 | +| `COMBINE_INSPECTOR_RESOURCES` | `YES` | `NO` | |
| 153 | +| `COMBINE_TEST_RESOURCES` | `NO` | `YES` | |
| 154 | + |
| 155 | +Note the inversion: engineering builds keep Inspector resources separate but combine test resources. |
| 156 | + |
| 157 | +**Solution:** Use a Debug or Release engineering build. |
| 158 | + |
| 159 | +## "I need to debug Inspector C++ backend code" |
| 160 | + |
| 161 | +### Identifying the correct process |
| 162 | + |
| 163 | +| Code location | Process | |
| 164 | +|--------------|---------| |
| 165 | +| `Source/WebKit/UIProcess/Inspector/` | UI Process (Safari/MiniBrowser) | |
| 166 | +| `Source/WebCore/inspector/agents/` | Inspected WebContent Process | |
| 167 | +| `Source/WebKit/WebProcess/Inspector/WebInspectorUI.cpp` | Inspector's own WebContent Process | |
| 168 | +| `Source/JavaScriptCore/inspector/` | WebContent Process (JSC layer) | |
| 169 | + |
| 170 | +### System console logging |
| 171 | + |
| 172 | +In debug builds, Inspector `console.log()` output is routed to the system console automatically. For release builds: |
| 173 | + |
| 174 | +```bash |
| 175 | +defaults write com.apple.Safari WebKitDebugLogsPageMessagesToSystemConsoleEnabled -bool true |
| 176 | +``` |
| 177 | + |
| 178 | +### WebKit logging channels |
| 179 | + |
| 180 | +```bash |
| 181 | +defaults write com.apple.Safari WebKit2Logging "Inspector=debug" |
| 182 | +defaults write com.apple.Safari WebCoreLogging "Inspector=debug" |
| 183 | +``` |
| 184 | + |
| 185 | +Replace `com.apple.Safari` with the appropriate bundle identifier. |
| 186 | + |
| 187 | +## Debugging in iOS Simulator |
| 188 | + |
| 189 | +1. Build: `Tools/Scripts/build-webkit --debug --ios-simulator` |
| 190 | +2. Launch: `Tools/Scripts/run-safari --debug --ios-simulator` |
| 191 | +3. On Mac, open Safari > **Develop > Simulator** to see inspectable pages. |
| 192 | + |
| 193 | +The Inspector frontend runs on macOS, connecting via the remote inspector protocol (`RemoteWebInspectorUI`/`RemoteWebInspectorUIProxy`). |
| 194 | + |
| 195 | +## Engineering and Debug settings reference |
| 196 | + |
| 197 | +### Engineering pane |
| 198 | + |
| 199 | +Available when `WI.engineeringSettingsAllowed()` returns true: |
| 200 | + |
| 201 | +| Setting | Description | |
| 202 | +|---------|-------------| |
| 203 | +| Allow editing UserAgent shadow trees | Permits DOM editing of browser-generated shadow DOM | |
| 204 | +| Show WebKit-internal execution contexts | Reveals internal contexts in the console picker | |
| 205 | +| Show WebKit-internal scripts | Shows internal scripts in the Sources tab | |
| 206 | +| Pause in WebKit-internal scripts | Allows breakpoints in internal scripts | |
| 207 | +| Show Internal Objects (Heap Snapshot) | Displays JSC-internal heap objects | |
| 208 | +| Show Private Symbols (Heap Snapshot) | Displays private symbol properties | |
| 209 | + |
| 210 | +### Debug pane |
| 211 | + |
| 212 | +Available in engineering builds when Debug UI is enabled (Option + Shift + Command + D): |
| 213 | + |
| 214 | +| Setting | Description | |
| 215 | +|---------|-------------| |
| 216 | +| Messages | Log all protocol messages to console | |
| 217 | +| Time Stats | Log protocol message timing data | |
| 218 | +| Log as Text | Force JSON text output instead of structured objects | |
| 219 | +| Outline focused element | Draw outline around focused Inspector UI element | |
| 220 | +| Layout Flashing | Draw borders when views perform layout | |
| 221 | +| Style editing debug mode | Enable verbose CSS editing logging | |
| 222 | +| Report Uncaught Exceptions | Show dialog on uncaught frontend exceptions | |
| 223 | +| Layout Direction | Override layout direction (LTR/RTL/System) | |
| 224 | + |
| 225 | +## Quick reference |
| 226 | + |
| 227 | +| I want to... | Do this | |
| 228 | +|--------------|---------| |
| 229 | +| Rebuild after changing Inspector JS/CSS | `make -C Source/WebInspectorUI/` | |
| 230 | +| Rebuild after changing Inspector C++ | `make GROUP=webkit debug` | |
| 231 | +| Reload Inspector without restarting page | Option + Shift + Cmd + R | |
| 232 | +| Toggle Debug UI | Option + Shift + Cmd + D | |
| 233 | +| See protocol messages interactively | Debug UI > click console icon | |
| 234 | +| See protocol messages in a test | `InspectorTest.debug()` or `ProtocolTest.debug()` | |
| 235 | +| Debug Inspector JS with a debugger | Settings > Experimental > "Allow Inspecting Web Inspector" | |
| 236 | +| Get readable stack traces | Use a Debug or Release build (`COMBINE_INSPECTOR_RESOURCES=NO`) | |
| 237 | +| Enable C++ logging | `defaults write <bundle-id> WebKit2Logging "Inspector=debug"` | |
| 238 | +| Enable engineering settings in release | `defaults write <bundle-id> WebKitDebugWebInspectorEngineeringSettingsAllowed -bool true` | |
| 239 | +| Verify an event name exists | `grep -r "WI.ClassName.Event.Name" Source/WebInspectorUI/` | |
| 240 | +| Capture protocol traffic to file | Debug UI > right-click > Protocol Debugging > Capture Trace | |
0 commit comments