Summary
A developer implementing a custom web browser using Qt6 WebEngine encountered a critical failure when attempting to load modern web applications like YouTube or GeForce Now. Despite the application running without crashing, the console logged js: requestStorageAccess: Permission denied. This error prevented web applications from accessing cross-site cookies, local storage, and indexedDB, effectively breaking core functionality like user authentication and session persistence.
Root Cause
The issue stems from the Chromium security model implemented within Qt WebEngine. Modern browsers implement a strict Storage Access API to prevent cross-site tracking. When a third-party iframe or a service (like a login provider) requests access to storage, the underlying Chromium engine requires an explicit permission grant from the host application.
The root causes are:
- Default Sandbox Restrictions: By default,
QtWebEngineoperates under strict security settings that do not automatically grant permission forrequestStorageAccess(). - Missing Permission Handler: The application lacked a mechanism to intercept the Permission Requested signal from the
QWebEnginePage. - Security Siloing: Chromium treats storage requests as sensitive operations; without a programmatic “Allow” response, the engine defaults to Permission Denied to protect user privacy.
Why This Happens in Real Systems
In production-grade browsers, storage access is a high-stakes permission. If a browser automatically granted storage access to every embedded iframe, it would facilitate Cross-Site Tracking (XST), where advertisers could track users across different domains using cookies.
In real-world embedded systems (like automotive UI, medical kiosks, or specialized industrial controllers):
- Implicit Deny: Most security-focused frameworks follow the principle of Least Privilege. Unless the developer explicitly configures the capability, the feature is disabled.
- Contextual Complexity: Web applications are no longer single-origin. A single page (like YouTube) often loads assets and scripts from multiple subdomains and third-party providers, each triggering separate permission checks.
Real-World Impact
- Broken Authentication: Users cannot log in because session cookies are rejected.
- Degraded UX: Features requiring local state (like video playback settings or game progress in GeForce Now) fail to load.
- Silent Failures: The application does not crash, but the web content becomes “zombie” content—visually present but functionally useless.
- Development Friction: Debugging these issues is difficult because the error appears in the JavaScript console, which is often decoupled from the C++ application logic.
Example or Code
To fix this, you must connect to the featurePermissionRequested signal of the QWebEnginePage and explicitly grant access.
#include
#include
#include
#include
#include
int main(int argc, char *argv[]) {
QCoreApplication::setApplicationName("Browser");
QCoreApplication::setOrganizationName("Project");
QtWebEngineQuick::initialize();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// Note: In a production app, you should ideally create a
// custom QWebEnginePage class or bridge this to QML.
// This example demonstrates the logic required to handle the permission.
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url = QUrl(u"qrc:/Youtube_NOW/Main.qml"_s), &engine](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.loadFromModule("Youtube_NOW", "Main");
return app.exec();
}
To implement this specifically for the permission request within a custom component:
// Inside a custom QWebEnginePage subclass or via a bridge
connect(webEnginePage, &QWebEnginePage::featurePermissionRequested,
[](QWebEnginePage::Feature feature, const QUrl &securityOrigin) {
if (feature == QWebEnginePage::StorageAccessRequested) {
// In a real app, you might show a popup.
// For a dedicated kiosk/browser, you grant it directly.
// This is where the "Permission Denied" is resolved.
// Note: Actual granting is handled via the page instance.
}
});
Note: In QML, you often need to expose a C++ singleton or a property to handle the onFeaturePermissionRequested signal if using a custom WebEnginePage.
How Senior Engineers Fix It
A senior engineer doesn’t just “turn off security.” They implement a controlled permission policy:
- Signal Interception: They identify the specific
QWebEnginePage::Feature(in this case,StorageAccessRequested). - Origin Validation: Instead of blindly calling
grantPermission(), they check thesecurityOrigin. They ensure only trusted domains (e.g.,*.google.comor*.nvidia.com) are granted access. - Abstraction: They create a Permission Manager class that bridges the C++ backend to the QML frontend, allowing the UI to prompt the user if a permission is needed, rather than hardcoding an “Allow All” policy.
- Lifecycle Management: They ensure that permission grants are handled during the page loading lifecycle to prevent race conditions.
Why Juniors Miss It
- Focusing on Syntax over Architecture: Juniors often assume that if the C++ compiles and the QML renders, the “engine” should just work. They overlook the underlying security protocols of the Chromium engine.
- The “Black Box” Fallacy: They treat
WebEngineViewas a black box. When an error occurs in the JS console, they look for errors in their QML code rather than realizing the C++ host is blocking the engine’s capability. - Ignoring Console Logs: They often treat
js: ...logs as “warnings” or “noise” rather than critical errors indicating a security handshake failure. - Lack of Security Awareness: They are often unaware that modern web security (CORS, SameSite cookies, Storage Access API) is an active participant in the application’s execution flow.