0af182baa2
The URL string was being deleted before being used, leading to a use-after-free occurring when it is used afterwards. Fix this by taking the string by const ref to extend its lifetime, ensuring it doesn't get deleted before use.
413 lines
14 KiB
C++
413 lines
14 KiB
C++
// Copyright 2020 yuzu Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
|
#include <QKeyEvent>
|
|
|
|
#include <QWebEngineProfile>
|
|
#include <QWebEngineScript>
|
|
#include <QWebEngineScriptCollection>
|
|
#include <QWebEngineSettings>
|
|
#include <QWebEngineUrlScheme>
|
|
#endif
|
|
|
|
#include "common/file_util.h"
|
|
#include "core/core.h"
|
|
#include "core/frontend/input_interpreter.h"
|
|
#include "input_common/keyboard.h"
|
|
#include "input_common/main.h"
|
|
#include "yuzu/applets/web_browser.h"
|
|
#include "yuzu/applets/web_browser_scripts.h"
|
|
#include "yuzu/main.h"
|
|
#include "yuzu/util/url_request_interceptor.h"
|
|
|
|
#ifdef YUZU_USE_QT_WEB_ENGINE
|
|
|
|
namespace {
|
|
|
|
constexpr int HIDButtonToKey(HIDButton button) {
|
|
switch (button) {
|
|
case HIDButton::DLeft:
|
|
case HIDButton::LStickLeft:
|
|
return Qt::Key_Left;
|
|
case HIDButton::DUp:
|
|
case HIDButton::LStickUp:
|
|
return Qt::Key_Up;
|
|
case HIDButton::DRight:
|
|
case HIDButton::LStickRight:
|
|
return Qt::Key_Right;
|
|
case HIDButton::DDown:
|
|
case HIDButton::LStickDown:
|
|
return Qt::Key_Down;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
} // Anonymous namespace
|
|
|
|
QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
|
|
InputCommon::InputSubsystem* input_subsystem_)
|
|
: QWebEngineView(parent), input_subsystem{input_subsystem_},
|
|
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
|
|
input_interpreter(std::make_unique<InputInterpreter>(system)),
|
|
default_profile{QWebEngineProfile::defaultProfile()},
|
|
global_settings{QWebEngineSettings::globalSettings()} {
|
|
QWebEngineScript gamepad;
|
|
QWebEngineScript window_nx;
|
|
|
|
gamepad.setName(QStringLiteral("gamepad_script.js"));
|
|
window_nx.setName(QStringLiteral("window_nx_script.js"));
|
|
|
|
gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
|
|
window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
|
|
|
|
gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
|
window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
|
|
|
gamepad.setWorldId(QWebEngineScript::MainWorld);
|
|
window_nx.setWorldId(QWebEngineScript::MainWorld);
|
|
|
|
gamepad.setRunsOnSubFrames(true);
|
|
window_nx.setRunsOnSubFrames(true);
|
|
|
|
default_profile->scripts()->insert(gamepad);
|
|
default_profile->scripts()->insert(window_nx);
|
|
|
|
default_profile->setRequestInterceptor(url_interceptor.get());
|
|
|
|
global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
|
|
global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
|
|
global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
|
|
global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
|
|
global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
|
|
global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
|
|
|
|
global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
|
|
|
|
connect(
|
|
page(), &QWebEnginePage::windowCloseRequested, page(),
|
|
[this] {
|
|
if (page()->url() == url_interceptor->GetRequestedURL()) {
|
|
SetFinished(true);
|
|
SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed);
|
|
}
|
|
},
|
|
Qt::QueuedConnection);
|
|
}
|
|
|
|
QtNXWebEngineView::~QtNXWebEngineView() {
|
|
SetFinished(true);
|
|
StopInputThread();
|
|
}
|
|
|
|
void QtNXWebEngineView::LoadLocalWebPage(const std::string& main_url,
|
|
const std::string& additional_args) {
|
|
is_local = true;
|
|
|
|
LoadExtractedFonts();
|
|
SetUserAgent(UserAgent::WebApplet);
|
|
SetFinished(false);
|
|
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
|
|
SetLastURL("http://localhost/");
|
|
StartInputThread();
|
|
|
|
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(main_url)).toString() +
|
|
QString::fromStdString(additional_args)));
|
|
}
|
|
|
|
void QtNXWebEngineView::LoadExternalWebPage(const std::string& main_url,
|
|
const std::string& additional_args) {
|
|
is_local = false;
|
|
|
|
SetUserAgent(UserAgent::WebApplet);
|
|
SetFinished(false);
|
|
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
|
|
SetLastURL("http://localhost/");
|
|
StartInputThread();
|
|
|
|
load(QUrl(QString::fromStdString(main_url) + QString::fromStdString(additional_args)));
|
|
}
|
|
|
|
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
|
|
const QString user_agent_str = [user_agent] {
|
|
switch (user_agent) {
|
|
case UserAgent::WebApplet:
|
|
default:
|
|
return QStringLiteral("WebApplet");
|
|
case UserAgent::ShopN:
|
|
return QStringLiteral("ShopN");
|
|
case UserAgent::LoginApplet:
|
|
return QStringLiteral("LoginApplet");
|
|
case UserAgent::ShareApplet:
|
|
return QStringLiteral("ShareApplet");
|
|
case UserAgent::LobbyApplet:
|
|
return QStringLiteral("LobbyApplet");
|
|
case UserAgent::WifiWebAuthApplet:
|
|
return QStringLiteral("WifiWebAuthApplet");
|
|
}
|
|
}();
|
|
|
|
QWebEngineProfile::defaultProfile()->setHttpUserAgent(
|
|
QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
|
|
"(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
|
|
.arg(user_agent_str));
|
|
}
|
|
|
|
bool QtNXWebEngineView::IsFinished() const {
|
|
return finished;
|
|
}
|
|
|
|
void QtNXWebEngineView::SetFinished(bool finished_) {
|
|
finished = finished_;
|
|
}
|
|
|
|
Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
|
|
return exit_reason;
|
|
}
|
|
|
|
void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
|
|
exit_reason = exit_reason_;
|
|
}
|
|
|
|
const std::string& QtNXWebEngineView::GetLastURL() const {
|
|
return last_url;
|
|
}
|
|
|
|
void QtNXWebEngineView::SetLastURL(std::string last_url_) {
|
|
last_url = std::move(last_url_);
|
|
}
|
|
|
|
QString QtNXWebEngineView::GetCurrentURL() const {
|
|
return url_interceptor->GetRequestedURL().toString();
|
|
}
|
|
|
|
void QtNXWebEngineView::hide() {
|
|
SetFinished(true);
|
|
StopInputThread();
|
|
|
|
QWidget::hide();
|
|
}
|
|
|
|
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
|
|
if (is_local) {
|
|
input_subsystem->GetKeyboard()->PressKey(event->key());
|
|
}
|
|
}
|
|
|
|
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
|
|
if (is_local) {
|
|
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
|
|
}
|
|
}
|
|
|
|
template <HIDButton... T>
|
|
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
|
|
const auto f = [this](HIDButton button) {
|
|
if (input_interpreter->IsButtonPressedOnce(button)) {
|
|
page()->runJavaScript(
|
|
QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
|
|
[&](const QVariant& variant) {
|
|
if (variant.toBool()) {
|
|
switch (button) {
|
|
case HIDButton::A:
|
|
SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
|
|
break;
|
|
case HIDButton::B:
|
|
SendKeyPressEvent(Qt::Key_B);
|
|
break;
|
|
case HIDButton::X:
|
|
SendKeyPressEvent(Qt::Key_X);
|
|
break;
|
|
case HIDButton::Y:
|
|
SendKeyPressEvent(Qt::Key_Y);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
page()->runJavaScript(
|
|
QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
|
|
.arg(static_cast<u8>(button)));
|
|
}
|
|
};
|
|
|
|
(f(T), ...);
|
|
}
|
|
|
|
template <HIDButton... T>
|
|
void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
|
|
const auto f = [this](HIDButton button) {
|
|
if (input_interpreter->IsButtonPressedOnce(button)) {
|
|
SendKeyPressEvent(HIDButtonToKey(button));
|
|
}
|
|
};
|
|
|
|
(f(T), ...);
|
|
}
|
|
|
|
template <HIDButton... T>
|
|
void QtNXWebEngineView::HandleWindowKeyButtonHold() {
|
|
const auto f = [this](HIDButton button) {
|
|
if (input_interpreter->IsButtonHeld(button)) {
|
|
SendKeyPressEvent(HIDButtonToKey(button));
|
|
}
|
|
};
|
|
|
|
(f(T), ...);
|
|
}
|
|
|
|
void QtNXWebEngineView::SendKeyPressEvent(int key) {
|
|
if (key == 0) {
|
|
return;
|
|
}
|
|
|
|
QCoreApplication::postEvent(focusProxy(),
|
|
new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier));
|
|
QCoreApplication::postEvent(focusProxy(),
|
|
new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier));
|
|
}
|
|
|
|
void QtNXWebEngineView::StartInputThread() {
|
|
if (input_thread_running) {
|
|
return;
|
|
}
|
|
|
|
input_thread_running = true;
|
|
input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
|
|
}
|
|
|
|
void QtNXWebEngineView::StopInputThread() {
|
|
if (is_local) {
|
|
QWidget::releaseKeyboard();
|
|
}
|
|
|
|
input_thread_running = false;
|
|
if (input_thread.joinable()) {
|
|
input_thread.join();
|
|
}
|
|
}
|
|
|
|
void QtNXWebEngineView::InputThread() {
|
|
// Wait for 1 second before allowing any inputs to be processed.
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
|
|
if (is_local) {
|
|
QWidget::grabKeyboard();
|
|
}
|
|
|
|
while (input_thread_running) {
|
|
input_interpreter->PollInput();
|
|
|
|
HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y,
|
|
HIDButton::L, HIDButton::R>();
|
|
|
|
HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
|
|
HIDButton::DDown, HIDButton::LStickLeft,
|
|
HIDButton::LStickUp, HIDButton::LStickRight,
|
|
HIDButton::LStickDown>();
|
|
|
|
HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
|
|
HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp,
|
|
HIDButton::LStickRight, HIDButton::LStickDown>();
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|
|
|
|
void QtNXWebEngineView::LoadExtractedFonts() {
|
|
QWebEngineScript nx_font_css;
|
|
QWebEngineScript load_nx_font;
|
|
|
|
const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath(
|
|
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))));
|
|
|
|
nx_font_css.setName(QStringLiteral("nx_font_css.js"));
|
|
load_nx_font.setName(QStringLiteral("load_nx_font.js"));
|
|
|
|
nx_font_css.setSourceCode(
|
|
QString::fromStdString(NX_FONT_CSS)
|
|
.arg(fonts_dir + QStringLiteral("/FontStandard.ttf"))
|
|
.arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf"))
|
|
.arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf"))
|
|
.arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf"))
|
|
.arg(fonts_dir + QStringLiteral("/FontKorean.ttf"))
|
|
.arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf"))
|
|
.arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf")));
|
|
load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
|
|
|
|
nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
|
|
load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
|
|
|
|
nx_font_css.setWorldId(QWebEngineScript::MainWorld);
|
|
load_nx_font.setWorldId(QWebEngineScript::MainWorld);
|
|
|
|
nx_font_css.setRunsOnSubFrames(true);
|
|
load_nx_font.setRunsOnSubFrames(true);
|
|
|
|
default_profile->scripts()->insert(nx_font_css);
|
|
default_profile->scripts()->insert(load_nx_font);
|
|
|
|
connect(
|
|
url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(),
|
|
[this] {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT));
|
|
},
|
|
Qt::QueuedConnection);
|
|
}
|
|
|
|
#endif
|
|
|
|
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
|
|
connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
|
|
&GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
|
|
connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
|
|
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
|
|
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
|
|
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
|
|
}
|
|
|
|
QtWebBrowser::~QtWebBrowser() = default;
|
|
|
|
void QtWebBrowser::OpenLocalWebPage(
|
|
const std::string& local_url, std::function<void()> extract_romfs_callback_,
|
|
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
|
|
extract_romfs_callback = std::move(extract_romfs_callback_);
|
|
callback = std::move(callback_);
|
|
|
|
const auto index = local_url.find('?');
|
|
|
|
if (index == std::string::npos) {
|
|
emit MainWindowOpenWebPage(local_url, "", true);
|
|
} else {
|
|
emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
|
|
}
|
|
}
|
|
|
|
void QtWebBrowser::OpenExternalWebPage(
|
|
const std::string& external_url,
|
|
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
|
|
callback = std::move(callback_);
|
|
|
|
const auto index = external_url.find('?');
|
|
|
|
if (index == std::string::npos) {
|
|
emit MainWindowOpenWebPage(external_url, "", false);
|
|
} else {
|
|
emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
|
|
false);
|
|
}
|
|
}
|
|
|
|
void QtWebBrowser::MainWindowExtractOfflineRomFS() {
|
|
extract_romfs_callback();
|
|
}
|
|
|
|
void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
|
|
std::string last_url) {
|
|
callback(exit_reason, last_url);
|
|
}
|