init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
81
Telegram/lib_webview/webview/platform/win/webview_win.cpp
Normal file
81
Telegram/lib_webview/webview/platform/win/webview_win.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_win.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webview/platform/win/webview_windows_edge_chromium.h"
|
||||
#include "webview/platform/win/webview_windows_edge_html.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Webview {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool SystemTooOld() {
|
||||
return !Platform::IsWindows8Point1OrGreater();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
base::unique_qptr<QWindow> MakeFramelessWindow() {
|
||||
auto result = base::make_unique_q<QWindow>();
|
||||
result->setFlag(Qt::FramelessWindowHint);
|
||||
return result;
|
||||
}
|
||||
|
||||
Available Availability() {
|
||||
if (SystemTooOld()) {
|
||||
return Available{
|
||||
.error = Available::Error::OldWindows,
|
||||
.details = "Please update your system to Windows 8.1 or later.",
|
||||
};
|
||||
} else if (EdgeChromium::Supported()) {
|
||||
return Available{
|
||||
.customSchemeRequests = true,
|
||||
.customRangeRequests = true,
|
||||
.customReferer = true,
|
||||
};
|
||||
} else if (EdgeHtml::Supported()) {
|
||||
return Available{};
|
||||
}
|
||||
return Available{
|
||||
.error = Available::Error::NoWebview2,
|
||||
.details = "Please install Microsoft Edge Webview2 Runtime.",
|
||||
};
|
||||
}
|
||||
|
||||
bool SupportsEmbedAfterCreate() {
|
||||
return !SystemTooOld()
|
||||
&& !EdgeChromium::Supported()
|
||||
&& EdgeHtml::Supported();
|
||||
}
|
||||
|
||||
bool SeparateStorageIdSupported() {
|
||||
return !SystemTooOld() && EdgeChromium::Supported();
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
if (SystemTooOld()) {
|
||||
return nullptr;
|
||||
} else if (auto result = EdgeChromium::CreateInstance(config)) {
|
||||
return result;
|
||||
}
|
||||
return EdgeHtml::CreateInstance(config);
|
||||
}
|
||||
|
||||
std::string GenerateStorageToken() {
|
||||
constexpr auto kSize = 16;
|
||||
auto result = std::string(kSize, ' ');
|
||||
base::RandomFill(result.data(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearStorageDataByToken(const std::string &token) {
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
18
Telegram/lib_webview/webview/platform/win/webview_win.h
Normal file
18
Telegram/lib_webview/webview/platform/win/webview_win.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "webview/webview_interface.h"
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace Webview {
|
||||
|
||||
[[nodiscard]] base::unique_qptr<QWindow> MakeFramelessWindow();
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,119 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_windows_data_stream.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
DataStreamCOM::DataStreamCOM(
|
||||
std::unique_ptr<DataStream> wrapped)
|
||||
: _wrapped(std::move(wrapped)) {
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Read(
|
||||
_Out_writes_bytes_to_(cb, *pcbRead) void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbRead) {
|
||||
const auto read = _wrapped->read(pv, cb);
|
||||
if (read < 0) {
|
||||
return E_FAIL;
|
||||
} else if (pcbRead) {
|
||||
*pcbRead = read;
|
||||
}
|
||||
return (read == cb) ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Write(
|
||||
_In_reads_bytes_(cb) const void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbWritten) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Seek(
|
||||
/* [in] */ LARGE_INTEGER dlibMove,
|
||||
/* [in] */ DWORD dwOrigin,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *plibNewPosition) {
|
||||
const auto origin = [&] {
|
||||
switch (dwOrigin) {
|
||||
case STREAM_SEEK_SET: return SEEK_SET;
|
||||
case STREAM_SEEK_CUR: return SEEK_CUR;
|
||||
case STREAM_SEEK_END: return SEEK_END;
|
||||
}
|
||||
return -1;
|
||||
}();
|
||||
if (origin < 0) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
const auto position = _wrapped->seek(origin, dlibMove.QuadPart);
|
||||
if (position < 0) {
|
||||
return E_FAIL;
|
||||
} else if (plibNewPosition) {
|
||||
plibNewPosition->QuadPart = position;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::SetSize(
|
||||
/* [in] */ ULARGE_INTEGER libNewSize) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::CopyTo(
|
||||
/* [annotation][unique][in] */
|
||||
_In_ IStream *pstm,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbRead,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbWritten) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Commit(
|
||||
/* [in] */ DWORD grfCommitFlags) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Revert(void) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::LockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::UnlockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Stat(
|
||||
/* [out] */ __RPC__out STATSTG *pstatstg,
|
||||
/* [in] */ DWORD grfStatFlag) {
|
||||
const auto size = _wrapped->size();
|
||||
if (size >= 0) {
|
||||
pstatstg->cbSize.QuadPart = size;
|
||||
return S_OK;
|
||||
}
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Clone(
|
||||
/* [out] */ __RPC__deref_out_opt IStream **ppstm) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,80 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/platform/win/base_windows_wrl.h"
|
||||
#include "base/platform/win/wrl/wrl_implements_h.h"
|
||||
#include "webview/webview_data_stream.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class DataStreamCOM final
|
||||
: public Microsoft::WRL::RuntimeClass<
|
||||
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
|
||||
IStream> {
|
||||
public:
|
||||
explicit DataStreamCOM(std::unique_ptr<DataStream> wrapped);
|
||||
~DataStreamCOM() = default;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Read(
|
||||
_Out_writes_bytes_to_(cb, *pcbRead) void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbRead) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Write(
|
||||
_In_reads_bytes_(cb) const void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbWritten) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(
|
||||
/* [in] */ LARGE_INTEGER dlibMove,
|
||||
/* [in] */ DWORD dwOrigin,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *plibNewPosition) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetSize(
|
||||
/* [in] */ ULARGE_INTEGER libNewSize) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE CopyTo(
|
||||
/* [annotation][unique][in] */
|
||||
_In_ IStream *pstm,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbRead,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbWritten) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Commit(
|
||||
/* [in] */ DWORD grfCommitFlags) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Revert(void) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE LockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE UnlockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Stat(
|
||||
/* [out] */ __RPC__out STATSTG *pstatstg,
|
||||
/* [in] */ DWORD grfStatFlag) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Clone(
|
||||
/* [out] */ __RPC__deref_out_opt IStream **ppstm) override;
|
||||
|
||||
private:
|
||||
const std::unique_ptr<DataStream> _wrapped;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,920 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_windows_edge_chromium.h"
|
||||
|
||||
#include "webview/webview_data_stream.h"
|
||||
#include "webview/webview_embed.h"
|
||||
#include "webview/platform/win/webview_windows_data_stream.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/basic_types.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/flat_map.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/options.h"
|
||||
#include "base/variant.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_co_task_mem.h"
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
#include "base/platform/win/wrl/wrl_implements_h.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <crl/common/crl_common_on_main_guarded.h>
|
||||
#include <rpl/variable.h>
|
||||
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <shlwapi.h>
|
||||
#include <WebView2.h>
|
||||
#include <WebView2EnvironmentOptions.h>
|
||||
|
||||
namespace Webview::EdgeChromium {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDataUrlPrefix
|
||||
= std::string_view("http://desktop-app-resource/");
|
||||
|
||||
[[nodiscard]] std::wstring ToWide(std::string_view string) {
|
||||
const auto length = MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
nullptr,
|
||||
0);
|
||||
auto result = std::wstring(length, wchar_t{});
|
||||
MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
result.data(),
|
||||
result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string FromWide(std::wstring_view string) {
|
||||
const auto length = WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr);
|
||||
auto result = std::string(length, char{});
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
result.data(),
|
||||
result.size(),
|
||||
nullptr,
|
||||
nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string FromWide(const base::CoTaskMemString &string) {
|
||||
return string ? FromWide(string.data()) : std::string();
|
||||
}
|
||||
|
||||
class Handler
|
||||
: public winrt::implements<
|
||||
Handler,
|
||||
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
|
||||
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
|
||||
ICoreWebView2WebMessageReceivedEventHandler,
|
||||
ICoreWebView2PermissionRequestedEventHandler,
|
||||
ICoreWebView2NavigationStartingEventHandler,
|
||||
ICoreWebView2NavigationCompletedEventHandler,
|
||||
ICoreWebView2ContentLoadingEventHandler,
|
||||
ICoreWebView2DocumentTitleChangedEventHandler,
|
||||
ICoreWebView2SourceChangedEventHandler,
|
||||
ICoreWebView2NewWindowRequestedEventHandler,
|
||||
ICoreWebView2ScriptDialogOpeningEventHandler,
|
||||
ICoreWebView2WebResourceRequestedEventHandler,
|
||||
ICoreWebView2ZoomFactorChangedEventHandler>
|
||||
, public ZoomController
|
||||
, public base::has_weak_ptr {
|
||||
|
||||
public:
|
||||
Handler(
|
||||
Config config,
|
||||
HWND handle,
|
||||
std::function<void(not_null<Handler*>)> saveThis,
|
||||
std::function<void()> readyHandler);
|
||||
~Handler();
|
||||
|
||||
const winrt::com_ptr<ICoreWebView2Environment> &environment() const {
|
||||
return _environment;
|
||||
}
|
||||
const winrt::com_ptr<ICoreWebView2Controller> &controller() const {
|
||||
return _controller;
|
||||
}
|
||||
const winrt::com_ptr<ICoreWebView2> &webview() const {
|
||||
return _webview;
|
||||
}
|
||||
[[nodiscard]] bool valid() const {
|
||||
return _window && _environment && _controller && _webview;
|
||||
}
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Environment *env) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Controller *controller) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebMessageReceivedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2PermissionRequestedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationStartingEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationCompletedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ContentLoadingEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
IUnknown *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2SourceChangedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NewWindowRequestedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ScriptDialogOpeningEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2Controller *sender,
|
||||
IUnknown *args) override;
|
||||
|
||||
rpl::producer<int> zoomValue() override {
|
||||
return _zoomValue.value();
|
||||
}
|
||||
void setZoom(int zoom) {
|
||||
if (_controller) {
|
||||
_controller->put_ZoomFactor(zoom / 100.);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<NavigationHistoryState> navigationHistoryState() {
|
||||
return _navigationHistoryState.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] ZoomController *zoomController() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
void updateHistoryStates();
|
||||
|
||||
HWND _window = nullptr;
|
||||
winrt::com_ptr<ICoreWebView2Environment> _environment;
|
||||
winrt::com_ptr<ICoreWebView2Controller> _controller;
|
||||
winrt::com_ptr<ICoreWebView2> _webview;
|
||||
std::function<void(std::string)> _messageHandler;
|
||||
std::function<bool(std::string, bool)> _navigationStartHandler;
|
||||
std::function<void(bool)> _navigationDoneHandler;
|
||||
std::function<DialogResult(DialogArgs)> _dialogHandler;
|
||||
std::function<DataResult(DataRequest)> _dataRequestHandler;
|
||||
std::function<void()> _readyHandler;
|
||||
base::flat_map<
|
||||
winrt::com_ptr<ICoreWebView2WebResourceRequestedEventArgs>,
|
||||
winrt::com_ptr<ICoreWebView2Deferral>> _pending;
|
||||
|
||||
rpl::variable<NavigationHistoryState> _navigationHistoryState;
|
||||
rpl::variable<int> _zoomValue;
|
||||
|
||||
QColor _opaqueBg;
|
||||
bool _debug = false;
|
||||
|
||||
};
|
||||
|
||||
Handler::Handler(
|
||||
Config config,
|
||||
HWND handle,
|
||||
std::function<void(not_null<Handler*>)> saveThis,
|
||||
std::function<void()> readyHandler)
|
||||
: _window(handle)
|
||||
, _messageHandler(std::move(config.messageHandler))
|
||||
, _navigationStartHandler(std::move(config.navigationStartHandler))
|
||||
, _navigationDoneHandler(std::move(config.navigationDoneHandler))
|
||||
, _dialogHandler(std::move(config.dialogHandler))
|
||||
, _dataRequestHandler(std::move(config.dataRequestHandler))
|
||||
, _readyHandler(std::move(readyHandler))
|
||||
, _opaqueBg(config.opaqueBg)
|
||||
, _debug(config.debug) {
|
||||
saveThis(this);
|
||||
setOpaqueBg(_opaqueBg);
|
||||
}
|
||||
|
||||
Handler::~Handler() = default;
|
||||
|
||||
void Handler::setOpaqueBg(QColor opaqueBg) {
|
||||
_opaqueBg = Platform::IsWindows10OrGreater()
|
||||
? QColor(255, 255, 255, 0)
|
||||
: opaqueBg;
|
||||
if (const auto late = _controller.try_as<ICoreWebView2Controller2>()) {
|
||||
late->put_DefaultBackgroundColor({
|
||||
uchar(_opaqueBg.alpha()),
|
||||
uchar(_opaqueBg.red()),
|
||||
uchar(_opaqueBg.green()),
|
||||
uchar(_opaqueBg.blue())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Environment *env) {
|
||||
_environment.copy_from(env);
|
||||
if (!_environment) {
|
||||
return E_FAIL;
|
||||
}
|
||||
_environment->CreateCoreWebView2Controller(_window, this);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Controller *controller) {
|
||||
if (!_readyHandler) {
|
||||
return S_OK;
|
||||
}
|
||||
_controller.copy_from(controller);
|
||||
const auto guard = gsl::finally([&] {
|
||||
const auto onstack = _readyHandler;
|
||||
_readyHandler = nullptr;
|
||||
onstack();
|
||||
});
|
||||
if (!_controller) {
|
||||
_window = nullptr;
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
_controller->get_CoreWebView2(_webview.put());
|
||||
if (!_webview) {
|
||||
return E_FAIL;
|
||||
}
|
||||
auto token = ::EventRegistrationToken();
|
||||
_webview->add_WebMessageReceived(this, &token);
|
||||
_webview->add_PermissionRequested(this, &token);
|
||||
_webview->add_NavigationStarting(this, &token);
|
||||
_webview->add_NavigationCompleted(this, &token);
|
||||
_webview->add_ContentLoading(this, &token);
|
||||
_webview->add_DocumentTitleChanged(this, &token);
|
||||
_webview->add_SourceChanged(this, &token);
|
||||
_webview->add_NewWindowRequested(this, &token);
|
||||
_webview->add_ScriptDialogOpening(this, &token);
|
||||
_webview->add_WebResourceRequested(this, &token);
|
||||
|
||||
_controller->add_ZoomFactorChanged(this, &token);
|
||||
|
||||
const auto filter = ToWide(kDataUrlPrefix) + L'*';
|
||||
auto hr = _webview->AddWebResourceRequestedFilter(
|
||||
filter.c_str(),
|
||||
COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
|
||||
if (hr != S_OK) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
auto settings = winrt::com_ptr<ICoreWebView2Settings>();
|
||||
hr = _webview->get_Settings(settings.put());
|
||||
if (hr != S_OK || !settings) {
|
||||
return E_FAIL;
|
||||
}
|
||||
settings->put_AreDefaultContextMenusEnabled(_debug);
|
||||
settings->put_AreDevToolsEnabled(_debug);
|
||||
settings->put_AreDefaultScriptDialogsEnabled(FALSE);
|
||||
settings->put_IsStatusBarEnabled(FALSE);
|
||||
|
||||
setOpaqueBg(_opaqueBg);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebMessageReceivedEventArgs *args) {
|
||||
auto message = base::CoTaskMemString();
|
||||
const auto result = args->TryGetWebMessageAsString(message.put());
|
||||
|
||||
if (result == S_OK && message) {
|
||||
_messageHandler(FromWide(message));
|
||||
sender->PostWebMessageAsString(message.data());
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2PermissionRequestedEventArgs *args) {
|
||||
auto kind = COREWEBVIEW2_PERMISSION_KIND{};
|
||||
const auto result = args->get_PermissionKind(&kind);
|
||||
if (result == S_OK) {
|
||||
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
|
||||
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationStartingEventArgs *args) {
|
||||
auto uri = base::CoTaskMemString();
|
||||
const auto result = args->get_Uri(uri.put());
|
||||
|
||||
if (result == S_OK && uri) {
|
||||
if (_navigationStartHandler
|
||||
&& !_navigationStartHandler(FromWide(uri), false)) {
|
||||
args->put_Cancel(TRUE);
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationCompletedEventArgs *args) {
|
||||
auto isSuccess = BOOL(FALSE);
|
||||
const auto result = args->get_IsSuccess(&isSuccess);
|
||||
|
||||
if (_navigationDoneHandler) {
|
||||
_navigationDoneHandler(result == S_OK && isSuccess);
|
||||
}
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ContentLoadingEventArgs *args) {
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
IUnknown *args) {
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2SourceChangedEventArgs *args) {
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NewWindowRequestedEventArgs *args) {
|
||||
auto uri = base::CoTaskMemString();
|
||||
const auto result = args->get_Uri(uri.put());
|
||||
auto isUserInitiated = BOOL{};
|
||||
args->get_IsUserInitiated(&isUserInitiated);
|
||||
args->put_Handled(TRUE);
|
||||
|
||||
if (result == S_OK && uri && isUserInitiated) {
|
||||
const auto url = FromWide(uri);
|
||||
if (_navigationStartHandler && _navigationStartHandler(url, true)) {
|
||||
QDesktopServices::openUrl(QString::fromStdString(url));
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ScriptDialogOpeningEventArgs *args) {
|
||||
auto kind = COREWEBVIEW2_SCRIPT_DIALOG_KIND_ALERT;
|
||||
auto hr = args->get_Kind(&kind);
|
||||
if (hr != S_OK) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
auto uri = base::CoTaskMemString();
|
||||
auto text = base::CoTaskMemString();
|
||||
auto value = base::CoTaskMemString();
|
||||
hr = args->get_Uri(uri.put());
|
||||
if (hr != S_OK || !uri) {
|
||||
return S_OK;
|
||||
}
|
||||
hr = args->get_Message(text.put());
|
||||
if (hr != S_OK || !text) {
|
||||
return S_OK;
|
||||
}
|
||||
hr = args->get_DefaultText(value.put());
|
||||
if (hr != S_OK || !value) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const auto type = [&] {
|
||||
switch (kind) {
|
||||
case COREWEBVIEW2_SCRIPT_DIALOG_KIND_ALERT:
|
||||
return DialogType::Alert;
|
||||
case COREWEBVIEW2_SCRIPT_DIALOG_KIND_CONFIRM:
|
||||
return DialogType::Confirm;
|
||||
case COREWEBVIEW2_SCRIPT_DIALOG_KIND_PROMPT:
|
||||
return DialogType::Prompt;
|
||||
}
|
||||
return DialogType::Alert;
|
||||
}();
|
||||
const auto result = _dialogHandler(DialogArgs{
|
||||
.type = type,
|
||||
.value = FromWide(value),
|
||||
.text = FromWide(text),
|
||||
.url = FromWide(uri),
|
||||
});
|
||||
|
||||
if (result.accepted) {
|
||||
args->Accept();
|
||||
if (kind == COREWEBVIEW2_SCRIPT_DIALOG_KIND_PROMPT) {
|
||||
const auto wide = ToWide(result.text);
|
||||
args->put_ResultText(wide.c_str());
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2Controller *sender,
|
||||
IUnknown *args) {
|
||||
auto zoom = float64(0);
|
||||
sender->get_ZoomFactor(&zoom);
|
||||
_zoomValue = zoom * 100;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args) {
|
||||
auto request = winrt::com_ptr<ICoreWebView2WebResourceRequest>();
|
||||
auto hr = args->get_Request(request.put());
|
||||
if (hr != S_OK || !request) {
|
||||
return S_OK;
|
||||
}
|
||||
auto uri = base::CoTaskMemString();
|
||||
hr = request->get_Uri(uri.put());
|
||||
if (hr != S_OK || !uri) {
|
||||
return S_OK;
|
||||
}
|
||||
auto headers = winrt::com_ptr<ICoreWebView2HttpRequestHeaders>();
|
||||
hr = request->get_Headers(headers.put());
|
||||
if (hr != S_OK || !headers) {
|
||||
return S_OK;
|
||||
}
|
||||
winrt::com_ptr<ICoreWebView2HttpHeadersCollectionIterator> iterator;
|
||||
hr = headers->GetIterator(iterator.put());
|
||||
if (hr != S_OK || !iterator) {
|
||||
return S_OK;
|
||||
}
|
||||
const auto ansi = FromWide(uri);
|
||||
const auto prefix = kDataUrlPrefix.size();
|
||||
if (ansi.size() <= prefix || ansi.compare(0, prefix, kDataUrlPrefix)) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
constexpr auto fail = [](
|
||||
ICoreWebView2Environment *environment,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args) {
|
||||
auto response = winrt::com_ptr<ICoreWebView2WebResourceResponse>();
|
||||
auto hr = environment->CreateWebResourceResponse(
|
||||
nullptr,
|
||||
404,
|
||||
L"Not Found",
|
||||
L"",
|
||||
response.put());
|
||||
if (hr == S_OK && response) {
|
||||
args->put_Response(response.get());
|
||||
}
|
||||
return S_OK;
|
||||
};
|
||||
constexpr auto done = [](
|
||||
ICoreWebView2Environment *environment,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args,
|
||||
DataResponse resolved) {
|
||||
auto &stream = resolved.stream;
|
||||
if (!stream) {
|
||||
return fail(environment, args);
|
||||
}
|
||||
const auto length = stream->size();
|
||||
const auto offset = resolved.streamOffset;
|
||||
const auto total = resolved.totalSize ? resolved.totalSize : length;
|
||||
const auto partial = (offset > 0) || (total != length);
|
||||
auto headers = L""
|
||||
L"Content-Type: " + ToWide(stream->mime()) +
|
||||
L"\nAccess-Control-Allow-Origin: *"
|
||||
L"\nAccept-Ranges: bytes"
|
||||
L"\nCache-Control: no-store"
|
||||
L"\nContent-Length: " + std::to_wstring(length);
|
||||
if (partial) {
|
||||
headers += L"\nContent-Range: bytes "
|
||||
+ std::to_wstring(offset)
|
||||
+ L'-'
|
||||
+ std::to_wstring(offset + length - 1)
|
||||
+ L'/'
|
||||
+ std::to_wstring(total);
|
||||
}
|
||||
auto response = winrt::com_ptr<ICoreWebView2WebResourceResponse>();
|
||||
auto hr = environment->CreateWebResourceResponse(
|
||||
Microsoft::WRL::Make<DataStreamCOM>(std::move(stream)).Detach(),
|
||||
partial ? 206 : 200,
|
||||
partial ? L"Partial Content" : L"OK",
|
||||
headers.c_str(),
|
||||
response.put());
|
||||
if (hr == S_OK && response) {
|
||||
args->put_Response(response.get());
|
||||
}
|
||||
return S_OK;
|
||||
};
|
||||
const auto callback = crl::guard(this, [=](DataResponse response) {
|
||||
done(_environment.get(), args, std::move(response));
|
||||
auto copy = winrt::com_ptr<ICoreWebView2WebResourceRequestedEventArgs>();
|
||||
copy.copy_from(args);
|
||||
if (const auto deferral = _pending.take(copy)) {
|
||||
(*deferral)->Complete();
|
||||
}
|
||||
});
|
||||
auto prepared = DataRequest{
|
||||
.id = ansi.substr(prefix),
|
||||
.done = std::move(callback),
|
||||
};
|
||||
while (true) {
|
||||
auto hasCurrent = BOOL();
|
||||
hr = iterator->get_HasCurrentHeader(&hasCurrent);
|
||||
if (hr != S_OK || !hasCurrent) {
|
||||
break;
|
||||
}
|
||||
auto name = base::CoTaskMemString();
|
||||
auto value = base::CoTaskMemString();
|
||||
hr = iterator->GetCurrentHeader(name.put(), value.put());
|
||||
if (hr != S_OK || !name || !value) {
|
||||
break;
|
||||
} else if (FromWide(name) == "Range") {
|
||||
const auto data = FromWide(value);
|
||||
ParseRangeHeaderFor(prepared, FromWide(value));
|
||||
}
|
||||
auto hasNext = BOOL();
|
||||
hr = iterator->MoveNext(&hasNext);
|
||||
if (hr != S_OK || !hasNext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto result = _dataRequestHandler
|
||||
? _dataRequestHandler(prepared)
|
||||
: DataResult::Failed;
|
||||
if (result == DataResult::Failed) {
|
||||
return fail(_environment.get(), args);
|
||||
} else if (result == DataResult::Pending) {
|
||||
auto deferral = winrt::com_ptr<ICoreWebView2Deferral>();
|
||||
hr = args->GetDeferral(deferral.put());
|
||||
if (hr != S_OK || !deferral) {
|
||||
return fail(_environment.get(), args);
|
||||
}
|
||||
auto copy = winrt::com_ptr<ICoreWebView2WebResourceRequestedEventArgs>();
|
||||
copy.copy_from(args);
|
||||
_pending.emplace(std::move(copy), std::move(deferral));
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void Handler::updateHistoryStates() {
|
||||
if (!_webview) {
|
||||
return;
|
||||
};
|
||||
auto canGoBack = BOOL(FALSE);
|
||||
auto canGoForward = BOOL(FALSE);
|
||||
auto url = base::CoTaskMemString();
|
||||
auto title = base::CoTaskMemString();
|
||||
_webview->get_CanGoBack(&canGoBack);
|
||||
_webview->get_CanGoForward(&canGoForward);
|
||||
_webview->get_Source(url.put());
|
||||
_webview->get_DocumentTitle(title.put());
|
||||
_navigationHistoryState = NavigationHistoryState{
|
||||
.url = FromWide(url),
|
||||
.title = FromWide(title),
|
||||
.canGoBack = (canGoBack == TRUE),
|
||||
.canGoForward = (canGoForward == TRUE),
|
||||
};
|
||||
}
|
||||
|
||||
class Instance final : public Interface, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Instance(Config &&config);
|
||||
~Instance();
|
||||
|
||||
[[nodiscard]] bool failed() const;
|
||||
|
||||
void navigate(std::string url) override;
|
||||
void navigateToData(std::string id) override;
|
||||
void reload() override;
|
||||
|
||||
void init(std::string js) override;
|
||||
void eval(std::string js) override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
QWidget *widget() override;
|
||||
|
||||
void refreshNavigationHistoryState() override;
|
||||
auto navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> override;
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg) override;
|
||||
|
||||
[[nodiscard]] ZoomController *zoomController() override;
|
||||
|
||||
private:
|
||||
struct NavigateToUrl {
|
||||
std::string url;
|
||||
};
|
||||
struct NavigateToData {
|
||||
std::string id;
|
||||
};
|
||||
struct InitScript {
|
||||
std::string script;
|
||||
};
|
||||
struct EvalScript {
|
||||
std::string script;
|
||||
};
|
||||
|
||||
using ReadyStep = std::variant<
|
||||
NavigateToUrl,
|
||||
NavigateToData,
|
||||
InitScript,
|
||||
EvalScript>;
|
||||
|
||||
void start(Config &&config);
|
||||
[[nodiscard]] bool ready() const;
|
||||
void processReadySteps();
|
||||
void resizeToWindow();
|
||||
|
||||
base::unique_qptr<QWindow> _window;
|
||||
HWND _handle = nullptr;
|
||||
winrt::com_ptr<IUnknown> _ownedHandler;
|
||||
Handler *_handler = nullptr;
|
||||
std::vector<ReadyStep> _waitingForReady;
|
||||
base::unique_qptr<QWidget> _widget;
|
||||
bool _pendingFocus = false;
|
||||
bool _readyFlag = false;
|
||||
|
||||
};
|
||||
|
||||
Instance::Instance(Config &&config)
|
||||
: _window(MakeFramelessWindow())
|
||||
, _handle(HWND(_window->winId()))
|
||||
, _widget(
|
||||
QWidget::createWindowContainer(
|
||||
_window,
|
||||
config.parent,
|
||||
Qt::FramelessWindowHint)) {
|
||||
_widget->show();
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
|
||||
start(std::move(config));
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
if (_handler) {
|
||||
if (_handler->valid()) {
|
||||
_handler->controller()->Close();
|
||||
}
|
||||
_handler = nullptr;
|
||||
}
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
void Instance::start(Config &&config) {
|
||||
auto options = winrt::com_ptr<ICoreWebView2EnvironmentOptions>(
|
||||
Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>().Detach(),
|
||||
winrt::take_ownership_from_abi);
|
||||
options->put_AdditionalBrowserArguments(
|
||||
L"--disable-features=ElasticOverscroll");
|
||||
|
||||
auto handler = (Handler*)nullptr;
|
||||
const auto ready = [=] {
|
||||
_readyFlag = true;
|
||||
if (_handler) {
|
||||
processReadySteps();
|
||||
}
|
||||
};
|
||||
auto owned = winrt::make<Handler>(
|
||||
config,
|
||||
_handle,
|
||||
[&](not_null<Handler*> that) { handler = that; },
|
||||
crl::guard(this, ready));
|
||||
const auto wpath = ToWide(config.userDataPath);
|
||||
const auto result = CreateCoreWebView2EnvironmentWithOptions(
|
||||
nullptr,
|
||||
wpath.empty() ? nullptr : wpath.c_str(),
|
||||
options.get(),
|
||||
handler);
|
||||
if (result == S_OK) {
|
||||
_ownedHandler = std::move(owned);
|
||||
_handler = handler;
|
||||
if (_readyFlag) {
|
||||
processReadySteps();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::failed() const {
|
||||
return !_handler;
|
||||
}
|
||||
|
||||
void Instance::processReadySteps() {
|
||||
Expects(ready());
|
||||
|
||||
const auto guard = base::make_weak(this);
|
||||
if (!_handler->valid()) {
|
||||
_widget = nullptr;
|
||||
_handle = nullptr;
|
||||
_window = nullptr;
|
||||
_handler = nullptr;
|
||||
base::take(_ownedHandler);
|
||||
return;
|
||||
}
|
||||
if (guard) {
|
||||
_handler->controller()->put_IsVisible(TRUE);
|
||||
}
|
||||
if (const auto widget = guard ? _widget.get() : nullptr) {
|
||||
base::install_event_filter(widget, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Resize || e->type() == QEvent::Move) {
|
||||
InvokeQueued(widget, [=] { resizeToWindow(); });
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
resizeToWindow();
|
||||
}
|
||||
if (guard) {
|
||||
for (const auto &step : base::take(_waitingForReady)) {
|
||||
v::match(step, [&](const NavigateToUrl &data) {
|
||||
navigate(data.url);
|
||||
}, [&](const NavigateToData &data) {
|
||||
navigateToData(data.id);
|
||||
}, [&](const InitScript &data) {
|
||||
init(data.script);
|
||||
}, [&](const EvalScript &data) {
|
||||
eval(data.script);
|
||||
});
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (guard && _pendingFocus) {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::ready() const {
|
||||
return _handle && _handler && _readyFlag;
|
||||
}
|
||||
|
||||
void Instance::navigate(std::string url) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(NavigateToUrl{ std::move(url) });
|
||||
return;
|
||||
}
|
||||
const auto wide = ToWide(url);
|
||||
_handler->webview()->Navigate(wide.c_str());
|
||||
}
|
||||
|
||||
void Instance::navigateToData(std::string id) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(NavigateToData{ std::move(id) });
|
||||
return;
|
||||
}
|
||||
auto full = std::string();
|
||||
full.reserve(kDataUrlPrefix.size() + id.size());
|
||||
full.append(kDataUrlPrefix);
|
||||
full.append(id);
|
||||
navigate(full);
|
||||
}
|
||||
|
||||
void Instance::reload() {
|
||||
if (ready()) {
|
||||
_handler->webview()->Reload();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::resizeToWindow() {
|
||||
auto bounds = RECT{};
|
||||
GetClientRect(_handle, &bounds);
|
||||
_handler->controller()->put_Bounds(bounds);
|
||||
}
|
||||
|
||||
void Instance::init(std::string js) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(InitScript{ std::move(js) });
|
||||
return;
|
||||
}
|
||||
const auto wide = ToWide(js);
|
||||
_handler->webview()->AddScriptToExecuteOnDocumentCreated(
|
||||
wide.c_str(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void Instance::eval(std::string js) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(EvalScript{ std::move(js) });
|
||||
return;
|
||||
}
|
||||
const auto wide = ToWide(js);
|
||||
_handler->webview()->ExecuteScript(wide.c_str(), nullptr);
|
||||
}
|
||||
|
||||
void Instance::focus() {
|
||||
if (_window) {
|
||||
_window->requestActivate();
|
||||
}
|
||||
if (_handle) {
|
||||
SetForegroundWindow(_handle);
|
||||
SetFocus(_handle);
|
||||
}
|
||||
if (!ready()) {
|
||||
_pendingFocus = true;
|
||||
return;
|
||||
}
|
||||
_handler->controller()->MoveFocus(
|
||||
COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
|
||||
}
|
||||
|
||||
QWidget *Instance::widget() {
|
||||
return _widget.get();
|
||||
}
|
||||
|
||||
void Instance::refreshNavigationHistoryState() {
|
||||
// Not needed here, there are events.
|
||||
}
|
||||
|
||||
auto Instance::navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> {
|
||||
return _handler
|
||||
? _handler->navigationHistoryState()
|
||||
: rpl::single(NavigationHistoryState());
|
||||
}
|
||||
|
||||
ZoomController *Instance::zoomController() {
|
||||
return _handler->zoomController();
|
||||
}
|
||||
|
||||
void Instance::setOpaqueBg(QColor opaqueBg) {
|
||||
if (_handler) {
|
||||
_handler->setOpaqueBg(opaqueBg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Supported() {
|
||||
if (base::options::value<bool>(kOptionWebviewLegacyEdge)) {
|
||||
return false;
|
||||
}
|
||||
auto version = LPWSTR(nullptr);
|
||||
const auto result = GetAvailableCoreWebView2BrowserVersionString(
|
||||
nullptr,
|
||||
&version);
|
||||
return (result == S_OK);
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
if (!Supported()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Instance>(std::move(config));
|
||||
return result->failed() ? nullptr : std::move(result);
|
||||
}
|
||||
|
||||
} // namespace Webview::EdgeChromium
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/platform/win/webview_win.h"
|
||||
|
||||
namespace Webview::EdgeChromium {
|
||||
|
||||
[[nodiscard]] bool Supported();
|
||||
[[nodiscard]] std::unique_ptr<Interface> CreateInstance(Config config);
|
||||
|
||||
} // namespace Webview::EdgeChromium
|
||||
@@ -0,0 +1,384 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_windows_edge_html.h"
|
||||
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/variant.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <crl/crl_on_main.h>
|
||||
#include <rpl/variable.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Web.UI.Interop.h>
|
||||
|
||||
namespace Webview::EdgeHtml {
|
||||
namespace {
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Web::UI;
|
||||
using namespace Windows::Web::UI::Interop;
|
||||
using namespace base::WinRT;
|
||||
|
||||
class Instance final : public Interface, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Instance(Config config);
|
||||
~Instance();
|
||||
|
||||
void navigate(std::string url) override;
|
||||
void navigateToData(std::string id) override;
|
||||
void reload() override;
|
||||
|
||||
void init(std::string js) override;
|
||||
void eval(std::string js) override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
QWidget *widget() override;
|
||||
|
||||
void refreshNavigationHistoryState() override;
|
||||
auto navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> override;
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg) override;
|
||||
|
||||
private:
|
||||
struct NavigateToUrl {
|
||||
std::string url;
|
||||
};
|
||||
struct EvalScript {
|
||||
std::string script;
|
||||
};
|
||||
struct SetOpaqueBg {
|
||||
QColor color;
|
||||
};
|
||||
|
||||
using ReadyStep = std::variant<
|
||||
NavigateToUrl,
|
||||
EvalScript,
|
||||
SetOpaqueBg>;
|
||||
|
||||
[[nodiscard]] bool ready() const;
|
||||
void ready(WebViewControl webview);
|
||||
void processReadySteps();
|
||||
void updateHistoryStates();
|
||||
|
||||
Config _config;
|
||||
base::unique_qptr<QWindow> _window;
|
||||
HWND _handle = nullptr;
|
||||
WebViewControlProcess _process;
|
||||
WebViewControl _webview = nullptr;
|
||||
base::unique_qptr<Ui::RpWidget> _widget;
|
||||
QPointer<QWidget> _embed;
|
||||
std::string _initScript;
|
||||
|
||||
rpl::variable<NavigationHistoryState> _navigationHistoryState;
|
||||
|
||||
std::vector<ReadyStep> _waitingForReady;
|
||||
bool _pendingFocus = false;
|
||||
bool _readyFlag = false;
|
||||
|
||||
};
|
||||
|
||||
Instance::Instance(Config config)
|
||||
: _config(std::move(config))
|
||||
, _window(MakeFramelessWindow())
|
||||
, _handle(HWND(_window->winId()))
|
||||
, _widget(base::make_unique_q<Ui::RpWidget>(config.parent)) {
|
||||
_widget->show();
|
||||
|
||||
setOpaqueBg(_config.opaqueBg);
|
||||
init("window.external.invoke = s => window.external.notify(s)");
|
||||
|
||||
init_apartment(apartment_type::single_threaded);
|
||||
const auto weak = base::make_weak(this);
|
||||
_process.CreateWebViewControlAsync(
|
||||
reinterpret_cast<int64_t>(_handle),
|
||||
Rect()
|
||||
).Completed([=](
|
||||
IAsyncOperation<WebViewControl> that,
|
||||
AsyncStatus status) {
|
||||
using namespace base::WinRT;
|
||||
const auto ok = (status == AsyncStatus::Completed) && Try([&] {
|
||||
crl::on_main([=, webview = that.GetResults()]() mutable {
|
||||
if (const auto that = weak.get()) {
|
||||
that->ready(std::move(webview));
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!ok) {
|
||||
crl::on_main([=] {
|
||||
if (const auto that = weak.get()) {
|
||||
that->_widget = nullptr;
|
||||
that->_handle = nullptr;
|
||||
that->_window = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_process.ProcessExited([=](const auto &sender, const auto &args) {
|
||||
_webview = nullptr;
|
||||
crl::on_main([=] {
|
||||
if (const auto that = weak.get()) {
|
||||
that->_embed = nullptr;
|
||||
that->_widget = nullptr;
|
||||
that->_handle = nullptr;
|
||||
that->_window = nullptr;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
if (ready()) {
|
||||
base::WinRT::Try([&] {
|
||||
std::exchange(_webview, WebViewControl(nullptr)).Close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::ready(WebViewControl webview) {
|
||||
auto guard = base::make_weak(this);
|
||||
_webview = std::move(webview);
|
||||
|
||||
_webview.ScriptNotify([handler = _config.messageHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlScriptNotifyEventArgs &args) {
|
||||
if (handler) {
|
||||
handler(winrt::to_string(args.Value()));
|
||||
}
|
||||
});
|
||||
_webview.NavigationStarting([=, handler = _config.navigationStartHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlNavigationStartingEventArgs &args) {
|
||||
if (handler
|
||||
&& !handler(winrt::to_string(args.Uri().AbsoluteUri()), false)) {
|
||||
args.Cancel(true);
|
||||
} else {
|
||||
_webview.AddInitializeScript(winrt::to_hstring(_initScript));
|
||||
}
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.ContentLoading([=](const auto &sender, const auto &args) {
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.DOMContentLoaded([=](const auto &sender, const auto &args) {
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.NavigationCompleted([=, handler = _config.navigationDoneHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlNavigationCompletedEventArgs &args) {
|
||||
if (handler) {
|
||||
handler(args.IsSuccess());
|
||||
}
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.NewWindowRequested([=, handler = _config.navigationStartHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlNewWindowRequestedEventArgs &args) {
|
||||
const auto url = winrt::to_string(args.Uri().AbsoluteUri());
|
||||
if (handler && handler(url, true)) {
|
||||
QDesktopServices::openUrl(QString::fromStdString(url));
|
||||
}
|
||||
});
|
||||
|
||||
_embed = QWidget::createWindowContainer(
|
||||
_window,
|
||||
_widget.get(),
|
||||
Qt::FramelessWindowHint);
|
||||
_embed->show();
|
||||
|
||||
_readyFlag = true;
|
||||
processReadySteps();
|
||||
}
|
||||
|
||||
void Instance::updateHistoryStates() {
|
||||
base::WinRT::Try([&] {
|
||||
_navigationHistoryState = NavigationHistoryState{
|
||||
.url = winrt::to_string(_webview.Source().AbsoluteUri()),
|
||||
.title = winrt::to_string(_webview.DocumentTitle()),
|
||||
.canGoBack = _webview.CanGoBack(),
|
||||
.canGoForward = _webview.CanGoForward(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
bool Instance::ready() const {
|
||||
return _handle && _webview && _readyFlag;
|
||||
}
|
||||
|
||||
void Instance::processReadySteps() {
|
||||
Expects(ready());
|
||||
|
||||
const auto guard = base::make_weak(this);
|
||||
if (!_webview) {
|
||||
_widget = nullptr;
|
||||
_handle = nullptr;
|
||||
_window = nullptr;
|
||||
return;
|
||||
}
|
||||
if (guard) {
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Settings().IsScriptNotifyAllowed(true);
|
||||
_webview.IsVisible(true);
|
||||
});
|
||||
}
|
||||
if (guard) {
|
||||
_widget->sizeValue() | rpl::on_next([=](QSize size) {
|
||||
_embed->setGeometry(QRect(QPoint(), size));
|
||||
|
||||
if (!ready()) {
|
||||
return;
|
||||
}
|
||||
RECT r;
|
||||
GetClientRect(_handle, &r);
|
||||
Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Bounds(bounds);
|
||||
});
|
||||
}, _widget->lifetime());
|
||||
}
|
||||
if (guard) {
|
||||
for (const auto &step : base::take(_waitingForReady)) {
|
||||
v::match(step, [&](const NavigateToUrl &data) {
|
||||
navigate(data.url);
|
||||
}, [&](const EvalScript &data) {
|
||||
eval(data.script);
|
||||
}, [&](const SetOpaqueBg &data) {
|
||||
setOpaqueBg(data.color);
|
||||
});
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (guard && _pendingFocus) {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::navigate(std::string url) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(NavigateToUrl{ std::move(url) });
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Navigate(Uri(winrt::to_hstring(url)));
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::navigateToData(std::string id) {
|
||||
Unexpected("EdgeHtml::Instance::navigateToData.");
|
||||
}
|
||||
|
||||
void Instance::reload() {
|
||||
if (!ready()) {
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::init(std::string js) {
|
||||
_initScript = _initScript + "(function(){" + js + "})();";
|
||||
}
|
||||
|
||||
void Instance::eval(std::string js) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(EvalScript{ std::move(js) });
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.InvokeScriptAsync(
|
||||
L"eval",
|
||||
single_threaded_vector<hstring>({ winrt::to_hstring(js) }));
|
||||
_webview.InvokeScriptAsync(
|
||||
L"eval",
|
||||
single_threaded_vector<hstring>({ winrt::to_hstring(
|
||||
"document.body.style.backgroundColor='transparent';") }));
|
||||
_webview.InvokeScriptAsync(
|
||||
L"eval",
|
||||
single_threaded_vector<hstring>({ winrt::to_hstring(
|
||||
"document.getElementsByTagName('html')[0].style.backgroundColor='transparent';") }));
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::focus() {
|
||||
if (_window) {
|
||||
_window->requestActivate();
|
||||
}
|
||||
if (_handle) {
|
||||
SetForegroundWindow(_handle);
|
||||
SetFocus(_handle);
|
||||
}
|
||||
if (!ready()) {
|
||||
_pendingFocus = true;
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.MoveFocus(WebViewControlMoveFocusReason::Programmatic);
|
||||
});
|
||||
}
|
||||
|
||||
QWidget *Instance::widget() {
|
||||
return _widget.get();
|
||||
}
|
||||
|
||||
void Instance::refreshNavigationHistoryState() {
|
||||
updateHistoryStates();
|
||||
}
|
||||
|
||||
auto Instance::navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> {
|
||||
return _navigationHistoryState.value();
|
||||
}
|
||||
|
||||
void Instance::setOpaqueBg(QColor opaqueBg) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(SetOpaqueBg{ opaqueBg });
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.DefaultBackgroundColor({
|
||||
uchar(opaqueBg.alpha()),
|
||||
uchar(opaqueBg.red()),
|
||||
uchar(opaqueBg.green()),
|
||||
uchar(opaqueBg.blue())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Supported() {
|
||||
return Try([&] {
|
||||
return (WebViewControlProcess() != nullptr);
|
||||
}).value_or(false);
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
return Try([&]() -> std::unique_ptr<Interface> {
|
||||
return std::make_unique<Instance>(std::move(config));
|
||||
}).value_or(nullptr);
|
||||
}
|
||||
|
||||
} // namespace Webview::EdgeHtml
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/platform/win/webview_win.h"
|
||||
|
||||
namespace Webview::EdgeHtml {
|
||||
|
||||
[[nodiscard]] bool Supported();
|
||||
[[nodiscard]] std::unique_ptr<Interface> CreateInstance(Config config);
|
||||
|
||||
} // namespace Webview::EdgeHtml
|
||||
Reference in New Issue
Block a user