Remove our implementation of the scripting api and use upstream's
version. It was recently moved to `extensions/` by
https://chromium-review.googlesource.com/c/chromium/src/+/7784831,
so we link it directly.
Update `ElectronExtensionsBrowserClient` to overrides `IsValidTabId()`
and `GetScriptExecutorForTab()` to provide tab validation and
script-executor hooks.
Remove now-redundant local copy of `scripting.idl`.
Upstream now provides everything we used this for.
Updated breaking-changes.md to document a CSS matching difference.
Co-authored-by: GitHub Copilot <github-copilot[bot]@users.noreply.github.com>
* fix: use the non-pass-through path for Fetch-intercepted requests
* Revert "fix: use the non-pass-through path for Fetch-intercepted requests"
This reverts commit 395fb8bb8c.
* fix: use no-op header client for Fetch-intercepted requests
* fix: bring back `DCHECK` that was prematurely removed
* style: reformat code
Replace ClientFrameViewLinux with electron::NativeFrameViewLinux, a thin
wrapper over views::NativeFrameViewLinux. The wrapper provides Electron
integration, such as draggable region support in NonClientHitTest,
and adapting to Electron's sizing conventions.
ElectronDesktopWindowTreeHostLinux and NativeWindowViews now use
FrameViewLinux to query frame geometry and update window states in
addition to LinuxFrameLayout.
Assisted-By: Claude Opus 4.6, Claude Code
* fix: validate header name and value in webRequest.onBeforeSendHeaders
Chromium's net::HttpRequestHeaders::SetHeader() uses CHECK() to enforce
valid header names and values, which causes a fatal crash if the caller
passes invalid strings. When users modify requestHeaders in the
onBeforeSendHeaders callback with invalid header names (e.g. containing
spaces) or invalid header values (e.g. containing CRLF), the
gin::Converter<net::HttpRequestHeaders>::FromV8() calls SetHeader()
directly, triggering the CHECK and crashing the process.
This change adds pre-validation using net::HttpUtil::IsValidHeaderName()
and net::HttpUtil::IsValidHeaderValue() before calling SetHeader(),
silently skipping invalid headers instead of crashing.
* Update shell/common/gin_converters/net_converter.cc
Co-authored-by: Charles Kerr <charles@charleskerr.com>
* Update spec/api-web-request-spec.ts
Co-authored-by: Charles Kerr <charles@charleskerr.com>
* fix: lint
---------
Co-authored-by: Charles Kerr <charles@charleskerr.com>
* chore: bump chromium in DEPS to 149.0.7812.0
* chore: update patches (trivial only)
Co-Authored-By: GitHub Copilot <copilot@github.com>
* fix(patch): declare abort in Node builtin_info
Node's builtin_info.cc uses abort() but doesn't include <cstdlib>.
It used to pick up the declaration by a transitive include, but
that broke in this libc++ roll.
This patch can be removed after it's been upstreamed to Node.js.
* SharedWorker: Enforce same-origin check for IWA and Extensions
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/7784632
* chore: node script/gen-libc++-filenames.js
---------
Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
* chore: bump chromium in DEPS to 149.0.7809.0
* chore: bump chromium in DEPS to 149.0.7810.2
* chore: bump chromium in DEPS to 149.0.7811.0
* chore: revert [OSCrypt] Remove sync backend
Electron still depends on the synchronous os_crypt API.
Revert upstream CL 7765593 until migration to async is complete.
Followup: https://github.com/electron/electron/issues/51301
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7765593
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* fix(patch): UAF fix in OnMouseRange
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7780978
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* fix(patch): kGlicTrustFirstOnboarding references removed
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7773143
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* chore: update patches (trivial only)
* fix(patch): SubtlePassKey and profile methods updates
Re-add OSCryptImpl as a friend of crypto::SubtlePassKey (removed by
https://chromium-review.googlesource.com/c/chromium/src/+/7759877)
since Electron still uses the sync backend.
Followup: https://github.com/electron/electron/issues/51301
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* fix(patch): exclude upstream scripting API
CL 7784831 moved the Scripting API from //chrome to //extensions,
which caused duplicate symbols with Electron's own implementation.
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7784831
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* 7748618: [extensions] Move MimeHandlerStreamManager
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7748618
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* 7713176: Move GetURLLoaderFactory from Profile to BrowserContext
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7713176
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* 7755340: Refactor CaptureHandle storage to PageImpl
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7755340
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* 7765593: [OSCrypt] Remove sync backend
No replacement code is needed: Electron already uses the async path.
CookieEncryptionProviderImpl (backed by OSCryptAsync) supplies
encryption to the network service via the cookie_encryption_provider
NetworkContext param, making SetEncryptionKey redundant.
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7765593
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* chore: stop disabling enterprise_cloud_content_analysis
CL 7757742 moved cloud_content_scanning from unconditional deps into
a conditional block gated on enterprise_cloud_content_analysis,
safe_browsing_mode, or is_android. Since Electron sets
safe_browsing_mode = 1, the dep is still included regardless, but
explicitly overriding enterprise_cloud_content_analysis to false now
causes other targets (e.g. chrome/browser/download) to omit enterprise
connectors code that the rest of the build expects to find.
It is simpler to let it default to true than to patch around it.
Electron does not use this feature — our PerformContentAnalysisIfNeeded
is a no-op passthrough that skips straight to NotifyListenerAndEnd.
Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7757742
Co-Authored-By: GitHub Copilot (Claude Opus 4.6)
* chore: update patches (trivial only)
* chore: update filenames.libcxx.gni
* chore: add GPU libraries to chromedriver zip manifests
Chromedriver now has transitive runtime dependencies on libEGL,
libGLESv2, and vk_swiftshader on macOS and Windows. These are
transitive deps pulled through chromedriver_server's dependency
on //mojo/core/embedder and //net.
* fix: add MicrotasksScope for worker exit emit in ContextWillDestroy
a39108c5a4 (#47244) replaced gin_helper::EmitEvent with a direct
`v8::Function::Call()` in `WebWorkerObserver::ContextWillDestroy`
to avoid re-entering the microtask checkpoint during worker teardown.
V8 `DCHECK()`s that a policy is set. Under the old code path, this
happened with a node::CallbackScope. Under the new code path, it's
possible for a policy to not be set, causing that `DCHECK()` to fail.
This PR copies a39108c5a4's changes in `ShareEnvironmentWithContext()`:
it explicitly adds a `kDoNotRunMicrotasks` scope.
* chore: override CreateChromeMetadataPacketRecorder in tracing delegate
https://chromium-review.googlesource.com/c/chromium/src/+/7770189
product-version, os-name, and channel metadata from the legacy
ChromeEventBundle path to a new ChromeMetadataPacket recorder callback.
Override the new TracingDelegate virtual so Electron still emits these fields.
---------
Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Added Browser::Get()->is_ready() guards to all contentTracing API functions (startRecording, stopRecording, getCategories, getTraceBufferUsage) so they reject their returned Promises with a clear error message instead of crashing when called before app.whenReady().
Added a crash-case fixture test that validates all four APIs reject properly before readiness and work normally after.
* perf: use GIO for Browser::IsDefaultProtocolClient() on Linux
perf: use GIO for Browser::SetAsDefaultProtocolClient() on Linux
Similar to 7d6227a, this speeds up app.isDefaultProtocolClient()
by using the GIO library instead of spawning a shell command to
get the info.
* feat: log errors if g_app_info_set_as_default_for_type() fails
a39108c5a4 (#47244) replaced gin_helper::EmitEvent with a direct
`v8::Function::Call()` in `WebWorkerObserver::ContextWillDestroy`
to avoid re-entering the microtask checkpoint during worker teardown.
V8 `DCHECK()`s that a policy is set. Under the old code path, this
happened with a node::CallbackScope. Under the new code path, it's
possible for a policy to not be set, causing that `DCHECK()` to fail.
This PR copies a39108c5a4's changes in `ShareEnvironmentWithContext()`:
it explicitly adds a `kDoNotRunMicrotasks` scope.
* fix: dispatch toast action and reply events from WinRT activation path
ToastEventHandler::Invoke previously returned S_OK without dispatching
whenever the activation arguments looked structured (type=action,
type=reply, or contained &tag=), on the assumption that the COM
INotificationActivationCallback::Activate path would deliver the event
instead. That assumption only holds when Windows actually invokes the
COM activator — which it does for MSIX-packaged apps launched cold, and
for unpackaged apps with a properly-registered CLSID when the app is
not already running. For non-MSIX apps with activationType="foreground"
while the app is running (the common case), Windows raises only the
in-process WinRT Activated event, so action and reply were silently
dropped.
Dispatch structured activations through the same HandleToastActivation
the COM path uses. User input (reply text, selection values) is pulled
from IToastActivatedEventArgs2::UserInput, which carries the data the
COM callback would otherwise have received via
NOTIFICATION_USER_INPUT_DATA.
Also drop the &tag= term from the structured-args check. Plain clicks
in Electron-generated XML don't carry tag=, and a custom toast_xml that
puts tag= on a click argument should now dispatch as a click rather
than being silently dropped.
* fix: release HSTRING out-params from toast activation
Fix a crash in AutofillPopupView::Show() when the popup
tried to show itself after the parent's native view had
already gone away during teardown.
2026-04-23T20:44:32.7015810Z Received signal 11 SEGV_ACCERR 000000000160
2026-04-23T20:44:32.9322010Z 4 Electron Framework ... views::Widget::IsVisible() const + 28
2026-04-23T20:44:32.9528810Z 6 Electron Framework ... electron::AutofillPopupView::Show() + 200
2026-04-23T20:44:32.9632090Z 7 Electron Framework ... electron::AutofillPopup::CreateView(...) + 1380
2026-04-23T20:44:32.9749770Z 8 Electron Framework ... electron::AutofillDriver::ShowAutofillPopup(...) + 736
2026-04-23T20:44:33.0015220Z ✗ Electron tests failed with kill signal SIGSEGV.
perf: use GIO instead of xdg-mime for app.getApplicationNameForProtocol()
The Linux impl of app.getApplicationNameForProtocol() now uses
`g_app_info_get_default_for_uri_scheme()` + `g_app_info_get_display_name()`
instead of spawning a call to the `xdg-mime` shell command.
Clean up the related tests: remove the xdg-mime mock.
* chore: remove calls to DeprecatedLayoutImmediately
Replace the calls to `DeprecatedLayoutImmediately` that have test
coverage.
- The docked DevTools test added last week covers the three calls in IWCV.
- api-web-contents-view-spec covers the calls from NativeWindow{Views,Mac}.
There are a couple of remaining calls that don't have test coverage yet.
I'll get to them in a followup.
* test: handle both sync or microtask layout
* refactor: add a FlushPendingRootLayout() helper
fix: ignore draggable regions in hidden WebContentsView
Hidden child WebContentsViews were still contributing their draggable
regions to the parent window's non-client hit test, so clicks in the
area where a hidden view's draggable element would render still dragged
the window. Early-return HTNOWHERE when the view is not visible.
After #49428 made `NativeWindowViews::CanResize()` return `resizable_`
for frameless windows (instead of `resizable_ && thick_frame_`),
`HWNDMessageHandler::SizeConstraintsChanged()` started adding
`WS_THICKFRAME` to the window style whenever `CanResize()` reported true.
`WS_THICKFRAME` is incompatible with layered (translucent) windows and
destroys their transparency.
`SetContentSizeConstraints` already guards against this by skipping
`OnSizeConstraintsChanged()` when `!thick_frame_`. `SetResizable` did
not, so toggling resizability on a transparent window (e.g.
`setResizable(false)` then `setResizable(true)`) caused the Chromium
path to add `WS_THICKFRAME` and strip transparency.
Apply the same guard in `SetResizable`. Min/max constraints are still
enforced — Chromium reads them from the widget delegate on every
`WM_GETMINMAXINFO`, independent of `SizeConstraintsChanged()`.
On ARM64 Windows, UnregisterSuspendResumeNotification (user32) forwards
to PowerUnregisterSuspendResumeNotification (powrprof), which treats the
HPOWERNOTIFY handle as a pointer and dereferences it. The user32 API
returns an opaque handle, not a pointer-backed allocation, causing an
access violation at shutdown.
Add crash keys (pm-reg-handle, pm-reg-memstate, pm-unreg-memstate) to
capture
- The handle value
- VirtualQuery memory state at both registration and unregistration
If the handle address is MEM_FREE, it confirms the handle is an opaque
index and powrprof is incorrectly dereferencing it. If MEM_COMMIT, it
would indicate a use-after-free of the underlying allocation.
Refs https://github.com/MicrosoftDocs/sdk-api/blob/docs/sdk-api-src/content/powerbase/nf-powerbase-powerunregistersuspendresumenotification.md
* ci: run clang-tidy on macOS and Windows
* ci: copy framework headers for clang-tidy on macOS
* chore: exclude electron_smooth_round_rect.cc in CI
* chore: C-style casts are discouraged; use static_cast [google-readability-casting]
* chore: add extra args on Windows to clear out warnings
* ci: fix for macOS --remote-build none
* fix: ensure corsEnabled: false protocol handlers do not work across protocols
Subresource requests for registered custom protocols are routed to
ElectronURLLoaderFactory via the renderer's per-scheme URLLoaderFactoryBundle
entry, which bypasses the network service's CorsURLLoaderFactory. This meant a
cross-origin page could fetch() a scheme registered with {supportFetchAPI: true}
and read the response body even when {corsEnabled: true} was not set.
Replicate CorsURLLoader::StartRequest's kCorsDisabledScheme gate in
ElectronURLLoaderFactory::CreateLoaderAndStart so cross-origin mode=cors
requests to such schemes fail before the JS handler runs, and tag cross-origin
mode=no-cors responses as opaque so the body is not script-readable while <img>
and similar subresource loads continue to work.
Re-enable the long-disabled "disallows CORS and fetch requests when only
supportFetchAPI is specified" test, add coverage for the opaque/no-cors,
same-origin, handler-not-invoked, corsEnabled-unaffected and net.fetch-unaffected
cases, and migrate spec helpers that were exercising a {supportFetchAPI: true}
scheme cross-origin to a corsEnabled scheme.
* chore: oxfmt
* fix: intermittent CI failure is-not-alwaysOnTop
Ensure that the `always-on-top-changed` event always fires with the
right 'alwaysOnTop' boolean, regardless of interaction between
SetZOrderLevel() and MoveBehindTaskBarIfNeeded(). We know what the
value will be when all of the HWND events settle, so use that value.
* test: temporary commit to torture-test the new change with 1000 iterations
* test: keep eventually-becomes-consistent test but do not loop 1000 times
* feat: add `Notification.getHistory()` static method (macOS)
Add `Notification.getHistory()` which returns a `Promise<Notification[]>`
of all delivered notifications still present in Notification Center.
Each returned Notification is a live object connected to the corresponding
delivered notification — interaction events (click, reply, action, close)
will fire on these objects, enabling apps to re-attach event handlers after
a restart.
Key implementation details:
- Queries UNUserNotificationCenter's getDeliveredNotifications API
- Creates live Notification objects with populated id, groupId, title,
subtitle, and body properties from what macOS provides
- Registers each object with the presenter via Restore() so the
NotificationCenterDelegate routes events correctly
- Restored notifications use is_restored_ flag to prevent removal from
Notification Center when the JS object is garbage collected
- Requires code-signed builds (unsigned builds resolve with empty array)
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
* test: fix typecheck
* fix: avoid dangling presenter pointer in GetHistory callback
* fix: document show() behavior
Notifications returned by getHistory() now set is_restored_ so that Dismiss() skips removal from Notification Center on GC. Calling show() on a restored notification removes the original from NC and posts a new one.
* fix: address code review feedback
* test: fix oxfmt linting
* docs: update docs/api/notification.md
Co-authored-by: Erick Zhao <erick@hotmail.ca>
---------
Co-authored-by: Claude <svc-devxp-claude@slack-corp.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
fix: prevent use-after-free when destroying guest WebContents during event emission
Multiple event emission sites in WebContents destroy the underlying C++
object via a JavaScript event handler calling webContents.destroy(), then
continue to dereference the freed `this` pointer. This is exploitable
through <webview> guest WebContents because Destroy() calls `delete this`
synchronously for guests, unlike non-guests which safely defer deletion.
The fix has two layers:
1. A new `is_emitting_event_` flag is checked in Destroy() — when true,
guest deletion is deferred to a posted task instead of executing
synchronously. This is separate from `is_safe_to_delete_` (which
gates LoadURL re-entrancy) to avoid rejecting legitimate loadURL
calls from event handlers.
2. AutoReset<bool> guards on `is_emitting_event_` are added to
CloseContents, RenderViewDeleted, DidFinishNavigation, and
SetContentsBounds, preventing synchronous destruction while their
Emit() calls are on the stack.
Destroy() now requires both `is_safe_to_delete_` (navigation re-entrancy)
and `!is_emitting_event_` (event emission) to allow synchronous guest
deletion. The existing AutoReset guards on `is_safe_to_delete_` in
DidStartNavigation, DidRedirectNavigation, and ReadyToCommitNavigation
are also now effective for guests.
* fix: UAF in api::UtilityProcessWrapper
Detach the wrapper from ServiceProcessHost during termination instead
of waiting for destruction. Add a regression test that forces GC.
This fixes a UAF error reported by ASAN: the wrapper lost its last JS
reference and become collectible after emitting exit *but* before it
had been removed from the global observer list.
UtilityProcessWrapper is now cppgc-managed as of b9e462f397, but its
ServiceProcessHost observer cleanup still depended on destructor-time
teardown.
* fixup! fix: UAF in api::UtilityProcessWrapper
fix: much better cleanup from Deepak code review
The PDF viewer's "save with changes" feature uses
`window.showSaveFilePicker()`, but the PDF extension runs in a
cross-origin iframe (chrome-extension:// inside the app's origin).
Chromium's File System Access API blocks cross-origin subframes from
showing file pickers unless the embedder explicitly allows them via
`ContentClient::IsFilePickerAllowedForCrossOriginSubframe()`.
Chrome overrides this in `ChromeContentClient` to allowlist the PDF
extension origin, but Electron never did — so the picker was always
blocked with a SecurityError.
This adds the same override to `ElectronContentClient`, allowing the
built-in PDF extension origin to bypass the cross-origin check.
* refactor: migrate electron::api::GlobalShortcut to cppgc
* refactor: lazy-create electron::api::GlobalShortcut
copy the lazy-create idom used by electron::api::Screen
* refactor: use gin::WeakCellFactory in GlobalCallbacks
* fix: make a copy of `callback` before running it
safeguard against the callback changing the map, invalidating `cb`
* chore: reduce unnecessary diffs with main
* fixup! refactor: use gin::WeakCellFactory in GlobalCallbacks
fix: must Trace() the weak cell factory
* fix: destruction order
- Setup isolate dispose observer to run destruction sequences
and remove self persistent reference
- Skip NOTREACHED check during destruction, it can happen
as a result of plaform listeners scheduling callbacks when Unregister is invoked.
- Fix the order of unregistration in GlobalShortcut::Unregister
- Add GlobalShortcut::UnregisterAllInternal to avoid any callsites
that can re-enter V8
* fix: crash during gc from incorrect cppgc object headers
* chore: update patches
* chore: cleanup
* chore: fix lint
---------
Co-authored-by: deepak1556 <hop2deep@gmail.com>
refactor: use StartUpdating in desktopCapturer
Replace the one-shot Update() callback model with the continuous
StartUpdating() observer model for NativeDesktopMediaList.
Fixes a macOS DCHECK(can_refresh()) crash in UpdateSourceThumbnail(),
where ScreenCaptureKit's recurrent thumbnail capturer would post
UpdateSourceThumbnail callbacks after the one-shot refresh_callback_
had been consumed. Now, can_refresh() is always true because
refresh_callback_ is repopulated via ScheduleNextRefresh().
Each capturer (window, screen) gets its own ListObserver that tracks
readiness via OnSourceAdded and OnSourceThumbnailChanged events.
Once a list has both sources and thumbnails (or thumbnails aren't
requested), its data is snapshotted and the capturer checks if all
requested types are ready before resolving to JS.
Also remove the "skip_next_refresh_" Chromium patch, which was a
workaround for the timing mismatch between the one-shot Update()
model and ScreenCaptureKit's asynchronous thumbnail delivery.
refactor: simplify state logic in DesktopCapturer
* fix: nodeIntegrationInWorker not working in AudioWorklet
* fix: deadlock on Windows when destroying non-AudioWorklet worker contexts
The previous change kept the WebWorkerObserver alive across
ContextWillDestroy so the worker thread could be reused for the next
context (AudioWorklet thread pooling, Chromium CL:5270028). This is
correct for AudioWorklet but wrong for PaintWorklet and other worker
types, which Blink does not pool — each teardown destroys the thread.
For those worker types, ~NodeBindings was deferred to the thread-exit
TLS callback. By that point set_uv_env(nullptr) had already run, so on
Windows the embed thread was parked in GetQueuedCompletionStatus with a
stale async_sent latch that swallowed the eventual WakeupEmbedThread()
from ~NodeBindings. uv_thread_join then blocked forever, deadlocking
renderer navigation. The worker-multiple-destroy crash case timed out
on win-x64/x86/arm64 as a result. macOS/Linux (epoll/kqueue) don't have
the latch and were unaffected.
Plumb is_audio_worklet from WillDestroyWorkerContextOnWorkerThread into
ContextWillDestroy. For non-AudioWorklet contexts, restore the
pre-existing behavior of calling lazy_tls->Set(nullptr) at the end of
the last-context cleanup so ~NodeBindings runs while the worker thread
is still healthy. AudioWorklet continues to keep the observer alive so
the next pooled context can share NodeBindings.
* chore: address review feedback
* fix: stop embed thread before destroying environments in worker teardown
FreeEnvironment (called via environments_.clear()) runs uv_run to drain
handle close callbacks. On Windows, both that uv_run and the embed
thread's PollEvents call GetQueuedCompletionStatus on the same IOCP
handle. IOCP completions are consumed by exactly one waiter, so the
embed thread can steal completions that FreeEnvironment needs, causing
uv_run to block indefinitely. On Linux/Mac epoll_wait/kevent can wake
multiple waiters for the same event so the race doesn't manifest.
Add NodeBindings::StopPolling() which cleanly joins the embed thread
without destroying handles or the loop, and allows PrepareEmbedThread +
StartPolling to restart it later. Call StopPolling() in
WebWorkerObserver::ContextWillDestroy before environments_.clear() so
FreeEnvironment's uv_run is the only thread touching the IOCP.
Split PrepareEmbedThread's handle initialization (uv_async_init,
uv_sem_init) from thread creation via a new embed_thread_prepared_ flag
so the handles survive across stop/restart cycles for pooled worklets
while the embed thread itself can be recreated.
* chore: address outstanding feedback
fix: simpleFullScreen exits when web content calls requestFullscreen
SetHtmlApiFullscreen only checked IsFullscreen() to detect that the
window was already fullscreen, missing the simple-fullscreen case on
macOS. When web content triggered requestFullscreen the code fell
through to SetFullScreen(true) which toggled simple fullscreen off.
Include IsSimpleFullScreen() in the guard so the HTML-API fullscreen
state is updated without touching the window's fullscreen mode.