Auto merge of #23981 - paulrouget:servocontrol, r=manishearth

Implement Servo Control Template

This wraps the swapchain in its own control, making it possible to better control the widget behavior.

In the future, we could package that control into a nuget package allowing any app to use Servo.

Fix #23894
Fix #23916

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23981)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2019-08-20 02:21:22 -04:00 committed by GitHub
commit 17f423723c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 522 additions and 336 deletions

View file

@ -7,261 +7,78 @@
#include "BrowserPage.h"
#include "BrowserPage.g.cpp"
#include "ImmersiveView.h"
#include "OpenGLES.h"
using namespace std::placeholders;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::ViewManagement;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Graphics::Holographic;
using namespace concurrency;
using namespace servo;
using namespace winrt::Windows::ApplicationModel::Core;
namespace winrt::ServoApp::implementation {
BrowserPage::BrowserPage() {
log("BrowserPage::BrowserPage()");
InitializeComponent();
InitializeConditionVariable(&mGLCondVar);
InitializeCriticalSection(&mGLLock);
Loaded(std::bind(&BrowserPage::OnPageLoaded, this, _1, _2));
Window::Current().CoreWindow().VisibilityChanged(
std::bind(&BrowserPage::OnVisibilityChanged, this, _1, _2));
BindServoEvents();
}
void BrowserPage::Shutdown() {
log("BrowserPage::Shutdown()");
if (mServo != nullptr) {
if (!mLooping) {
// FIXME: this should not happen. In that case, we can't send the
// shutdown event to Servo.
} else {
RunOnGLThread([=] { mServo->RequestShutdown(); });
mLoopTask->wait();
mLoopTask.reset();
mServo.reset();
}
}
}
void BrowserPage::OnPageLoaded(IInspectable const &, RoutedEventArgs const &) {
log("BrowserPage::OnPageLoaded()");
CreateRenderSurface();
StartRenderLoop();
swapChainPanel().PointerReleased(
std::bind(&BrowserPage::OnSurfaceClicked, this, _1, _2));
swapChainPanel().ManipulationDelta(
std::bind(&BrowserPage::OnSurfaceManipulationDelta, this, _1, _2));
}
void BrowserPage::OnVisibilityChanged(CoreWindow const &,
VisibilityChangedEventArgs const &args) {
auto visible = args.Visible();
// FIXME: for now, this is disabled as we get this message before shutdown,
// stopping the event loop, which we can't recover from yet (see comment in
// Loop())
// if (visible && !mLooping) {
// StartRenderLoop();
//}
// if (!visible && mLooping) {
// StopRenderLoop();
//}
}
void BrowserPage::CreateRenderSurface() {
if (mRenderSurface == EGL_NO_SURFACE) {
mRenderSurface = mOpenGLES.CreateSurface(swapChainPanel());
}
}
void BrowserPage::DestroyRenderSurface() {
mOpenGLES.DestroySurface(mRenderSurface);
mRenderSurface = EGL_NO_SURFACE;
}
void BrowserPage::RecoverFromLostDevice() {
StopRenderLoop();
DestroyRenderSurface();
mOpenGLES.Reset();
CreateRenderSurface();
StartRenderLoop();
}
/**** GL THREAD LOOP ****/
void BrowserPage::Loop() {
log("BrowserPage::Loop(). GL thread: %i", GetCurrentThreadId());
mOpenGLES.MakeCurrent(mRenderSurface);
EGLint panelWidth = 0;
EGLint panelHeight = 0;
mOpenGLES.GetSurfaceDimensions(mRenderSurface, &panelWidth, &panelHeight);
glViewport(0, 0, panelWidth, panelHeight);
if (mServo == nullptr) {
log("Entering loop");
ServoDelegate *sd = static_cast<ServoDelegate *>(this);
mServo = std::make_unique<Servo>(panelWidth, panelHeight, *sd);
} else {
// FIXME: this will fail since create_task didn't pick the thread
// where Servo was running initially.
throw winrt::hresult_error(E_FAIL, L"Recovering loop unimplemented");
}
mServo->SetBatchMode(true);
while (true) {
EnterCriticalSection(&mGLLock);
while (mTasks.size() == 0 && !mAnimating && mLooping) {
SleepConditionVariableCS(&mGLCondVar, &mGLLock, INFINITE);
}
if (!mLooping) {
LeaveCriticalSection(&mGLLock);
break;
}
for (auto &&task : mTasks) {
task();
}
mTasks.clear();
LeaveCriticalSection(&mGLLock);
mServo->PerformUpdates();
}
mServo->DeInit();
cancel_current_task();
} // namespace winrt::ServoApp::implementation
void BrowserPage::StartRenderLoop() {
if (mLooping) {
#if defined _DEBUG
throw winrt::hresult_error(E_FAIL, L"GL thread is already looping");
#else
return;
#endif
}
mLooping = true;
log("BrowserPage::StartRenderLoop(). UI thread: %i", GetCurrentThreadId());
auto task = Concurrency::create_task([=] { Loop(); });
mLoopTask = std::make_unique<Concurrency::task<void>>(task);
}
void BrowserPage::StopRenderLoop() {
if (mLooping) {
EnterCriticalSection(&mGLLock);
mLooping = false;
LeaveCriticalSection(&mGLLock);
WakeConditionVariable(&mGLCondVar);
mLoopTask->wait();
mLoopTask.reset();
}
}
/**** SERVO CALLBACKS ****/
void BrowserPage::OnLoadStarted() {
RunOnUIThread([=] {
void BrowserPage::BindServoEvents() {
servoControl().OnURLChanged(
[=](const auto &, hstring url) { urlTextbox().Text(url); });
servoControl().OnTitleChanged([=](const auto &, hstring title) {});
servoControl().OnHistoryChanged([=](bool back, bool forward) {
backButton().IsEnabled(back);
forwardButton().IsEnabled(forward);
});
servoControl().OnLoadStarted([=] {
reloadButton().IsEnabled(false);
stopButton().IsEnabled(true);
});
}
void BrowserPage::OnLoadEnded() {
RunOnUIThread([=] {
servoControl().OnLoadEnded([=] {
reloadButton().IsEnabled(true);
stopButton().IsEnabled(false);
});
}
void BrowserPage::OnHistoryChanged(bool back, bool forward) {
RunOnUIThread([=] {
backButton().IsEnabled(back);
forwardButton().IsEnabled(forward);
});
}
void BrowserPage::OnShutdownComplete() {
EnterCriticalSection(&mGLLock);
mLooping = false;
LeaveCriticalSection(&mGLLock);
}
void BrowserPage::OnAlert(std::wstring message) {
// FIXME: make this sync
RunOnUIThread([=] {
Windows::UI::Popups::MessageDialog msg{message};
msg.ShowAsync();
});
}
void BrowserPage::OnTitleChanged(std::wstring title) {
RunOnUIThread([=] { ApplicationView::GetForCurrentView().Title(title); });
}
void BrowserPage::OnURLChanged(std::wstring url) {
RunOnUIThread([=] { urlTextbox().Text(url); });
}
void BrowserPage::Flush() {
if (mOpenGLES.SwapBuffers(mRenderSurface) != GL_TRUE) {
// The call to eglSwapBuffers might not be successful (i.e. due to Device
// Lost) If the call fails, then we must reinitialize EGL and the GL
// resources.
RunOnUIThread([=] { RecoverFromLostDevice(); });
}
}
void BrowserPage::MakeCurrent() { mOpenGLES.MakeCurrent(mRenderSurface); }
void BrowserPage::WakeUp() {
RunOnGLThread([=] { });
}
bool BrowserPage::OnAllowNavigation(std::wstring) { return true; }
void BrowserPage::OnAnimatingChanged(bool animating) {
EnterCriticalSection(&mGLLock);
mAnimating = animating;
LeaveCriticalSection(&mGLLock);
WakeConditionVariable(&mGLCondVar);
}
template <typename Callable> void BrowserPage::RunOnUIThread(Callable cb) {
swapChainPanel().Dispatcher().RunAsync(
Windows::UI::Core::CoreDispatcherPriority::High, cb);
void BrowserPage::Shutdown() {
servoControl().Shutdown();
}
/**** USER INTERACTIONS WITH UI ****/
void BrowserPage::OnBackButtonClicked(IInspectable const &,
RoutedEventArgs const &) {
RunOnGLThread([=] { mServo->GoBack(); });
servoControl().GoBack();
}
void BrowserPage::OnForwardButtonClicked(IInspectable const &,
RoutedEventArgs const &) {
RunOnGLThread([=] { mServo->GoForward(); });
servoControl().GoForward();
}
void BrowserPage::OnReloadButtonClicked(IInspectable const &,
RoutedEventArgs const &) {
RunOnGLThread([=] { mServo->Reload(); });
servoControl().Reload();
}
void BrowserPage::OnStopButtonClicked(IInspectable const &,
RoutedEventArgs const &) {
RunOnGLThread([=] { mServo->Stop(); });
servoControl().Stop();
}
void BrowserPage::OnURLEdited(IInspectable const &sender,
Input::KeyRoutedEventArgs const &e) {
if (e.Key() == Windows::System::VirtualKey::Enter) {
servoControl().Focus(FocusState::Programmatic);
auto input = urlTextbox().Text();
auto uri = servoControl().LoadURIOrSearch(input);
urlTextbox().Text(uri.ToString());
}
}
void BrowserPage::OnImmersiveButtonClicked(IInspectable const &,
RoutedEventArgs const &) {
if (HolographicSpace::IsAvailable()) {
log("Holographic space available");
auto v =
winrt::Windows::ApplicationModel::Core::CoreApplication::CreateNewView(
mImmersiveViewSource);
auto v = CoreApplication::CreateNewView(mImmersiveViewSource);
auto parentId = ApplicationView::GetForCurrentView().Id();
v.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=] {
auto winId = ApplicationView::GetForCurrentView().Id();
@ -273,30 +90,4 @@ void BrowserPage::OnImmersiveButtonClicked(IInspectable const &,
}
}
void BrowserPage::OnSurfaceManipulationDelta(
IInspectable const &, Input::ManipulationDeltaRoutedEventArgs const &e) {
auto x = e.Position().X;
auto y = e.Position().Y;
auto dx = e.Delta().Translation.X;
auto dy = e.Delta().Translation.Y;
RunOnGLThread([=] { mServo->Scroll(dx, dy, x, y); });
e.Handled(true);
}
void BrowserPage::OnSurfaceClicked(IInspectable const &,
Input::PointerRoutedEventArgs const &e) {
auto coords = e.GetCurrentPoint(swapChainPanel());
auto x = coords.Position().X;
auto y = coords.Position().Y;
RunOnGLThread([=] { mServo->Click(x, y); });
e.Handled(true);
}
void BrowserPage::RunOnGLThread(std::function<void()> task) {
EnterCriticalSection(&mGLLock);
mTasks.push_back(task);
LeaveCriticalSection(&mGLLock);
WakeConditionVariable(&mGLCondVar);
}
} // namespace winrt::ServoApp::implementation

View file

@ -6,13 +6,12 @@
#include "BrowserPage.g.h"
#include "ImmersiveView.h"
#include "OpenGLES.h"
#include "Servo.h"
#include "ServoControl\ServoControl.h"
namespace winrt::ServoApp::implementation {
struct BrowserPage : BrowserPageT<BrowserPage>,
public servo::ServoDelegate {
struct BrowserPage : BrowserPageT<BrowserPage> {
public:
BrowserPage();
@ -26,59 +25,13 @@ public:
Windows::UI::Xaml::RoutedEventArgs const &);
void OnStopButtonClicked(Windows::Foundation::IInspectable const &,
Windows::UI::Xaml::RoutedEventArgs const &);
void
OnSurfaceClicked(Windows::Foundation::IInspectable const &,
Windows::UI::Xaml::Input::PointerRoutedEventArgs const &);
void BrowserPage::OnSurfaceManipulationDelta(
IInspectable const &,
Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs const &e);
template <typename Callable> void RunOnUIThread(Callable);
void RunOnGLThread(std::function<void()>);
void OnURLEdited(Windows::Foundation::IInspectable const &,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const &);
void Shutdown();
virtual void WakeUp();
virtual void OnLoadStarted();
virtual void OnLoadEnded();
virtual void OnHistoryChanged(bool, bool);
virtual void OnShutdownComplete();
virtual void OnTitleChanged(std::wstring);
virtual void OnAlert(std::wstring);
virtual void OnURLChanged(std::wstring);
virtual void Flush();
virtual void MakeCurrent();
virtual bool OnAllowNavigation(std::wstring);
virtual void OnAnimatingChanged(bool);
private:
void OnVisibilityChanged(
Windows::UI::Core::CoreWindow const &,
Windows::UI::Core::VisibilityChangedEventArgs const &args);
void OnPageLoaded(Windows::Foundation::IInspectable const &,
Windows::UI::Xaml::RoutedEventArgs const &);
void CreateRenderSurface();
void DestroyRenderSurface();
void RecoverFromLostDevice();
void StartRenderLoop();
void StopRenderLoop();
void Loop();
std::unique_ptr<Concurrency::task<void>> mLoopTask;
winrt::ServoApp::ImmersiveViewSource mImmersiveViewSource;
EGLSurface mRenderSurface{EGL_NO_SURFACE};
std::unique_ptr<servo::Servo> mServo;
std::vector<std::function<void()>> mTasks;
CRITICAL_SECTION mGLLock;
CONDITION_VARIABLE mGLCondVar;
bool mAnimating = false;
bool mLooping = false;
OpenGLES mOpenGLES; // FIXME: shared pointer
void BindServoEvents();
};
} // namespace winrt::ServoApp::implementation

View file

@ -20,17 +20,16 @@
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<Button x:Name="backButton" IsEnabled="false" Content="Back" Click="OnBackButtonClicked"/>
<Button x:Name="forwardButton" IsEnabled="false" Content="Forward" Click="OnForwardButtonClicked"/>
<Button x:Name="reloadButton" IsEnabled="false" Content="reload" Click="OnReloadButtonClicked"/>
<Button x:Name="stopButton" IsEnabled="false" Content="stop" Click="OnStopButtonClicked"/>
<Button x:Name="backButton" IsTabStop="true" IsEnabled="false" Content="Back" Click="OnBackButtonClicked"/>
<Button x:Name="forwardButton" IsTabStop="true" IsEnabled="false" Content="Forward" Click="OnForwardButtonClicked"/>
<Button x:Name="reloadButton" IsTabStop="true" IsEnabled="false" Content="reload" Click="OnReloadButtonClicked"/>
<Button x:Name="stopButton" IsTabStop="true" IsEnabled="false" Content="stop" Click="OnStopButtonClicked"/>
</StackPanel>
<TextBox Text="" AcceptsReturn="True" PlaceholderText="Type a URL" x:Name="urlTextbox" Grid.Column="1" IsReadOnly="True"/>
<TextBox Text="" IsTabStop="true" InputScope="Url" PlaceholderText="Type a URL" x:Name="urlTextbox" Grid.Column="1" KeyUp="OnURLEdited"/>
<StackPanel Orientation="Horizontal" Grid.Column="2">
<Button x:Name="immersiveButton" Click="OnImmersiveButtonClicked">Run Immersive</Button>
</StackPanel>
</Grid>
<SwapChainPanel x:Name="swapChainPanel" MinHeight="200" MinWidth="200" Grid.Row="1" ManipulationMode="All">
</SwapChainPanel>
<local:ServoControl TabIndex="0" x:Name="servoControl" Grid.Row="1"/>
</Grid>
</Page>

View file

@ -4,7 +4,6 @@
#pragma once
#include "OpenGLES.h"
#include "Common/DeviceResources.h"
#include "ImmersiveMain.h"

View file

@ -151,7 +151,6 @@
<ClInclude Include="ImmersiveMain.h" />
<ClInclude Include="ImmersiveView.h" />
<ClInclude Include="logs.h" />
<ClInclude Include="OpenGLES.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="App.h">
<DependentUpon>App.xaml</DependentUpon>
@ -159,7 +158,9 @@
<ClInclude Include="BrowserPage.h">
<DependentUpon>BrowserPage.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="Servo.h" />
<ClInclude Include="ServoControl\OpenGLES.h" />
<ClInclude Include="ServoControl\Servo.h" />
<ClInclude Include="ServoControl\ServoControl.h" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
@ -168,6 +169,9 @@
<Page Include="BrowserPage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="Themes\Generic.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
@ -294,7 +298,6 @@
<ClCompile Include="Content\SpinningCubeRenderer.cpp" />
<ClCompile Include="ImmersiveMain.cpp" />
<ClCompile Include="ImmersiveView.cpp" />
<ClCompile Include="OpenGLES.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -306,7 +309,9 @@
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="logs.cpp" />
<ClCompile Include="Servo.cpp" />
<ClCompile Include="ServoControl\OpenGLES.cpp" />
<ClCompile Include="ServoControl\Servo.cpp" />
<ClCompile Include="ServoControl\ServoControl.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="App.idl">
@ -315,6 +320,7 @@
<Midl Include="BrowserPage.idl">
<DependentUpon>BrowserPage.xaml</DependentUpon>
</Midl>
<Midl Include="ServoControl\ServoControl.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View file

@ -3,6 +3,9 @@
<ItemGroup>
<Midl Include="BrowserPage.idl" />
<Midl Include="App.idl" />
<Midl Include="ServoControl\ServoControl.idl">
<Filter>ServoControl</Filter>
</Midl>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp" />
@ -10,8 +13,6 @@
<ClCompile Include="logs.cpp" />
<ClCompile Include="ImmersiveView.cpp" />
<ClCompile Include="BrowserPage.cpp" />
<ClCompile Include="Servo.cpp" />
<ClCompile Include="OpenGLES.cpp" />
<ClCompile Include="App.cpp" />
<ClCompile Include="Common\CameraResources.cpp">
<Filter>Common</Filter>
@ -26,14 +27,21 @@
<Filter>Content</Filter>
</ClCompile>
<ClCompile Include="ImmersiveMain.cpp" />
<ClCompile Include="ServoControl\OpenGLES.cpp">
<Filter>ServoControl</Filter>
</ClCompile>
<ClCompile Include="ServoControl\Servo.cpp">
<Filter>ServoControl</Filter>
</ClCompile>
<ClCompile Include="ServoControl\ServoControl.cpp">
<Filter>ServoControl</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="logs.h" />
<ClInclude Include="ImmersiveView.h" />
<ClInclude Include="BrowserPage.h" />
<ClInclude Include="Servo.h" />
<ClInclude Include="OpenGLES.h" />
<ClInclude Include="App.h" />
<ClInclude Include="Common\CameraResources.h">
<Filter>Common</Filter>
@ -57,6 +65,15 @@
<Filter>Content</Filter>
</ClInclude>
<ClInclude Include="ImmersiveMain.h" />
<ClInclude Include="ServoControl\OpenGLES.h">
<Filter>ServoControl</Filter>
</ClInclude>
<ClInclude Include="ServoControl\Servo.h">
<Filter>ServoControl</Filter>
</ClInclude>
<ClInclude Include="ServoControl\ServoControl.h">
<Filter>ServoControl</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="Assets\Wide310x150Logo.scale-200.png">
@ -191,12 +208,18 @@
<Filter Include="ReleaseARM64ServoDLLs">
<UniqueIdentifier>{384b4019-d076-4301-994d-a891969a3036}</UniqueIdentifier>
</Filter>
<Filter Include="ServoControl">
<UniqueIdentifier>{d21a959c-19d1-4a54-b942-692c27e5b3a6}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<Page Include="BrowserPage.xaml" />
<Page Include="Themes\Generic.xaml">
<Filter>ServoControl</Filter>
</Page>
</ItemGroup>
<ItemGroup>
<FxCompile Include="Content\GeometryShader.hlsl">

View file

@ -1,31 +1,31 @@
#include "pch.h"
#include "Servo.h"
namespace servo {
namespace winrt::servo {
void on_load_started() { sServo->Delegate().OnLoadStarted(); }
void on_load_ended() { sServo->Delegate().OnLoadEnded(); }
void on_load_started() { sServo->Delegate().OnServoLoadStarted(); }
void on_load_ended() { sServo->Delegate().OnServoLoadEnded(); }
void on_history_changed(bool back, bool forward) {
sServo->Delegate().OnHistoryChanged(back, forward);
sServo->Delegate().OnServoHistoryChanged(back, forward);
}
void on_shutdown_complete() { sServo->Delegate().OnShutdownComplete(); }
void on_shutdown_complete() { sServo->Delegate().OnServoShutdownComplete(); }
void on_alert(const char *message) {
sServo->Delegate().OnAlert(char2w(message));
sServo->Delegate().OnServoAlert(char2hstring(message));
}
void on_title_changed(const char *title) {
sServo->Delegate().OnTitleChanged(char2w(title));
sServo->Delegate().OnServoTitleChanged(char2hstring(title));
}
void on_url_changed(const char *url) {
sServo->Delegate().OnURLChanged(char2w(url));
sServo->Delegate().OnServoURLChanged(char2hstring(url));
}
void flush() { sServo->Delegate().Flush(); }
void make_current() { sServo->Delegate().MakeCurrent(); }
void wakeup() { sServo->Delegate().WakeUp(); }
bool on_allow_navigation(const char *url) {
return sServo->Delegate().OnAllowNavigation(char2w(url));
return sServo->Delegate().OnServoAllowNavigation(char2hstring(url));
};
void on_animating_changed(bool aAnimating) {
sServo->Delegate().OnAnimatingChanged(aAnimating);
sServo->Delegate().OnServoAnimatingChanged(aAnimating);
}
Servo::Servo(GLsizei width, GLsizei height, ServoDelegate &aDelegate)
@ -60,14 +60,14 @@ Servo::Servo(GLsizei width, GLsizei height, ServoDelegate &aDelegate)
Servo::~Servo() { sServo = nullptr; }
std::wstring char2w(const char *c_str) {
winrt::hstring char2hstring(const char *c_str) {
// FIXME: any better way of doing this?
auto str = std::string(c_str);
int size_needed =
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
std::wstring str2(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &str2[0],
size_needed);
return str2;
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &str2[0], size_needed);
winrt::hstring str3 {str2};
return str3;
}
} // namespace servo

View file

@ -6,8 +6,9 @@
#include "pch.h"
#include "logs.h"
#include <stdlib.h>
namespace servo {
namespace winrt::servo {
namespace capi {
extern "C" {
@ -20,17 +21,17 @@ public:
// Called from any thread
virtual void WakeUp() = 0;
// Called from GL thread
virtual void OnLoadStarted() = 0;
virtual void OnLoadEnded() = 0;
virtual void OnHistoryChanged(bool, bool) = 0;
virtual void OnShutdownComplete() = 0;
virtual void OnTitleChanged(std::wstring) = 0;
virtual void OnAlert(std::wstring) = 0;
virtual void OnURLChanged(std::wstring) = 0;
virtual void OnServoLoadStarted() = 0;
virtual void OnServoLoadEnded() = 0;
virtual void OnServoHistoryChanged(bool, bool) = 0;
virtual void OnServoShutdownComplete() = 0;
virtual void OnServoTitleChanged(hstring) = 0;
virtual void OnServoAlert(hstring) = 0;
virtual void OnServoURLChanged(hstring) = 0;
virtual bool OnServoAllowNavigation(hstring) = 0;
virtual void OnServoAnimatingChanged(bool) = 0;
virtual void Flush() = 0;
virtual void MakeCurrent() = 0;
virtual bool OnAllowNavigation(std::wstring) = 0;
virtual void OnAnimatingChanged(bool) = 0;
protected:
virtual ~ServoDelegate(){};
@ -51,6 +52,14 @@ public:
void Click(float x, float y) { capi::click(x, y); }
void Reload() { capi::reload(); }
void Stop() { capi::stop(); }
void LoadUri(hstring uri) {
const wchar_t* wc = uri.c_str();
size_t size = uri.size() + 1;
char* str = new char[size];
size_t converted = 0;
wcstombs_s(&converted, str, size, wc, uri.size());
capi::load_uri(str);
}
void Scroll(float dx, float dy, float x, float y) {
capi::scroll(dx, dy, x, y);
}
@ -73,6 +82,6 @@ private:
// the Servo instance. See https://github.com/servo/servo/issues/22967
static Servo *sServo = nullptr;
std::wstring char2w(const char *c_str);
hstring char2hstring(const char *c_str);
} // namespace servo

View file

@ -0,0 +1,257 @@
#include "pch.h"
#include "ServoControl.h"
#include "ServoControl.g.cpp"
#include <stdlib.h>
using namespace std::placeholders;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::Foundation;
using namespace concurrency;
using namespace winrt::servo;
namespace winrt::ServoApp::implementation {
ServoControl::ServoControl() {
DefaultStyleKey(winrt::box_value(L"ServoApp.ServoControl"));
Loaded(std::bind(&ServoControl::OnLoaded, this, _1, _2));
}
void ServoControl::Shutdown() {
if (mServo != nullptr) {
if (!mLooping) {
// FIXME: this should not happen. In that case, we can't send the
// shutdown event to Servo.
} else {
RunOnGLThread([=] { mServo->RequestShutdown(); });
mLoopTask->wait();
mLoopTask.reset();
mServo.reset();
}
}
}
void ServoControl::OnLoaded(IInspectable const &, RoutedEventArgs const &) {
Panel().PointerReleased(
std::bind(&ServoControl::OnSurfaceClicked, this, _1, _2));
Panel().ManipulationDelta(
std::bind(&ServoControl::OnSurfaceManipulationDelta, this, _1, _2));
InitializeConditionVariable(&mGLCondVar);
InitializeCriticalSection(&mGLLock);
CreateRenderSurface();
StartRenderLoop();
}
Controls::SwapChainPanel ServoControl::Panel() {
// FIXME: is there a better way of doing this?
return GetTemplateChild(L"swapChainPanel")
.as<Controls::SwapChainPanel>();
}
void ServoControl::CreateRenderSurface() {
if (mRenderSurface == EGL_NO_SURFACE) {
mRenderSurface = mOpenGLES.CreateSurface(Panel());
}
}
void ServoControl::DestroyRenderSurface() {
mOpenGLES.DestroySurface(mRenderSurface);
mRenderSurface = EGL_NO_SURFACE;
}
void ServoControl::RecoverFromLostDevice() {
StopRenderLoop();
DestroyRenderSurface();
mOpenGLES.Reset();
CreateRenderSurface();
StartRenderLoop();
}
void ServoControl::OnSurfaceManipulationDelta(
IInspectable const &, Input::ManipulationDeltaRoutedEventArgs const &e) {
auto x = e.Position().X;
auto y = e.Position().Y;
auto dx = e.Delta().Translation.X;
auto dy = e.Delta().Translation.Y;
RunOnGLThread([=] { mServo->Scroll(dx, dy, x, y); });
e.Handled(true);
}
void ServoControl::OnSurfaceClicked(IInspectable const &,
Input::PointerRoutedEventArgs const &e) {
auto coords = e.GetCurrentPoint(Panel());
auto x = coords.Position().X;
auto y = coords.Position().Y;
RunOnGLThread([=] { mServo->Click(x, y); });
e.Handled(true);
}
void ServoControl::GoBack() {
RunOnGLThread([=] { mServo->GoBack(); });
}
void ServoControl::GoForward() {
RunOnGLThread([=] { mServo->GoForward(); });
}
void ServoControl::Reload() {
RunOnGLThread([=] { mServo->Reload(); });
}
void ServoControl::Stop() {
RunOnGLThread([=] { mServo->Stop(); });
}
Uri ServoControl::LoadURIOrSearch(hstring input) {
auto uri = TryParseURI(input);
if (uri == std::nullopt) {
bool has_dot = wcsstr(input.c_str(), L".") != nullptr;
hstring input2 = L"https://" + input;
uri = TryParseURI(input2);
if (uri == std::nullopt || !has_dot) {
hstring input3 = L"https://duckduckgo.com/html/?q=" + Uri::EscapeComponent(input);
uri = TryParseURI(input3);
}
}
auto finalUri = uri.value();
RunOnGLThread([=] { mServo->LoadUri(finalUri.ToString()); });
return finalUri;
}
void ServoControl::RunOnGLThread(std::function<void()> task) {
EnterCriticalSection(&mGLLock);
mTasks.push_back(task);
LeaveCriticalSection(&mGLLock);
WakeConditionVariable(&mGLCondVar);
}
/**** GL THREAD LOOP ****/
void ServoControl::Loop() {
log("BrowserPage::Loop(). GL thread: %i", GetCurrentThreadId());
mOpenGLES.MakeCurrent(mRenderSurface);
EGLint panelWidth = 0;
EGLint panelHeight = 0;
mOpenGLES.GetSurfaceDimensions(mRenderSurface, &panelWidth, &panelHeight);
glViewport(0, 0, panelWidth, panelHeight);
if (mServo == nullptr) {
log("Entering loop");
ServoDelegate *sd = static_cast<ServoDelegate *>(this);
mServo = std::make_unique<Servo>(panelWidth, panelHeight, *sd);
} else {
// FIXME: this will fail since create_task didn't pick the thread
// where Servo was running initially.
throw winrt::hresult_error(E_FAIL, L"Recovering loop unimplemented");
}
mServo->SetBatchMode(true);
while (true) {
EnterCriticalSection(&mGLLock);
while (mTasks.size() == 0 && !mAnimating && mLooping) {
SleepConditionVariableCS(&mGLCondVar, &mGLLock, INFINITE);
}
if (!mLooping) {
LeaveCriticalSection(&mGLLock);
break;
}
for (auto &&task : mTasks) {
task();
}
mTasks.clear();
LeaveCriticalSection(&mGLLock);
mServo->PerformUpdates();
}
mServo->DeInit();
cancel_current_task();
}
void ServoControl::StartRenderLoop() {
if (mLooping) {
#if defined _DEBUG
throw winrt::hresult_error(E_FAIL, L"GL thread is already looping");
#else
return;
#endif
}
mLooping = true;
log("BrowserPage::StartRenderLoop(). UI thread: %i", GetCurrentThreadId());
auto task = Concurrency::create_task([=] { Loop(); });
mLoopTask = std::make_unique<Concurrency::task<void>>(task);
}
void ServoControl::StopRenderLoop() {
if (mLooping) {
EnterCriticalSection(&mGLLock);
mLooping = false;
LeaveCriticalSection(&mGLLock);
WakeConditionVariable(&mGLCondVar);
mLoopTask->wait();
mLoopTask.reset();
}
}
/**** SERVO CALLBACKS ****/
void ServoControl::OnServoLoadStarted() {
RunOnUIThread([=] { mOnLoadStartedEvent(); });
}
void ServoControl::OnServoLoadEnded() {
RunOnUIThread([=] { mOnLoadEndedEvent(); });
}
void ServoControl::OnServoHistoryChanged(bool back, bool forward) {
RunOnUIThread([=] { mOnHistoryChangedEvent(back, forward); });
}
void ServoControl::OnServoShutdownComplete() {
EnterCriticalSection(&mGLLock);
mLooping = false;
LeaveCriticalSection(&mGLLock);
}
void ServoControl::OnServoAlert(hstring message) {
// FIXME: make this sync
RunOnUIThread([=] {
Windows::UI::Popups::MessageDialog msg{message};
msg.ShowAsync();
});
}
void ServoControl::OnServoTitleChanged(hstring title) {
RunOnUIThread([=] { mOnTitleChangedEvent(*this, title); });
}
void ServoControl::OnServoURLChanged(hstring url) {
RunOnUIThread([=] { mOnURLChangedEvent(*this, url); });
}
void ServoControl::Flush() {
if (mOpenGLES.SwapBuffers(mRenderSurface) != GL_TRUE) {
// The call to eglSwapBuffers might not be successful (i.e. due to Device
// Lost) If the call fails, then we must reinitialize EGL and the GL
// resources.
RunOnUIThread([=] { RecoverFromLostDevice(); });
}
}
void ServoControl::MakeCurrent() { mOpenGLES.MakeCurrent(mRenderSurface); }
void ServoControl::WakeUp() {
RunOnGLThread([=] {});
}
bool ServoControl::OnServoAllowNavigation(hstring) { return true; }
void ServoControl::OnServoAnimatingChanged(bool animating) {
EnterCriticalSection(&mGLLock);
mAnimating = animating;
LeaveCriticalSection(&mGLLock);
WakeConditionVariable(&mGLCondVar);
}
template <typename Callable> void ServoControl::RunOnUIThread(Callable cb) {
Dispatcher().RunAsync(CoreDispatcherPriority::High, cb);
}
} // namespace winrt::ServoApp::implementation

View file

@ -0,0 +1,110 @@
#pragma once
#include "ServoControl.g.h"
#include "OpenGLES.h"
#include "Servo.h"
namespace winrt::ServoApp::implementation {
struct ServoControl : ServoControlT<ServoControl>, public servo::ServoDelegate {
ServoControl();
void GoBack();
void GoForward();
void Reload();
void Stop();
void Shutdown();
Windows::Foundation::Uri LoadURIOrSearch(hstring);
void OnLoaded(IInspectable const &, Windows::UI::Xaml::RoutedEventArgs const &);
winrt::event_token
OnURLChanged(Windows::Foundation::EventHandler<hstring> const &handler){
return mOnURLChangedEvent.add(handler);
};
void OnURLChanged(winrt::event_token const& token) noexcept { mOnURLChangedEvent.remove(token); }
winrt::event_token
OnTitleChanged(Windows::Foundation::EventHandler<hstring> const &handler){
return mOnTitleChangedEvent.add(handler);
};
void OnTitleChanged(winrt::event_token const& token) noexcept { mOnTitleChangedEvent.remove(token); }
winrt::event_token OnHistoryChanged(HistoryChangedDelegate const &handler){
return mOnHistoryChangedEvent.add(handler);
};
void OnHistoryChanged(winrt::event_token const& token) noexcept { mOnHistoryChangedEvent.remove(token); }
winrt::event_token OnLoadStarted(LoadStatusChangedDelegate const &handler){
return mOnLoadStartedEvent.add(handler);
};
void OnLoadStarted(winrt::event_token const& token) noexcept { mOnLoadStartedEvent.remove(token); }
winrt::event_token OnLoadEnded(LoadStatusChangedDelegate const &handler){
return mOnLoadEndedEvent.add(handler);
};
void OnLoadEnded(winrt::event_token const& token) noexcept { mOnLoadEndedEvent.remove(token); }
virtual void WakeUp();
virtual void OnServoLoadStarted();
virtual void OnServoLoadEnded();
virtual void OnServoHistoryChanged(bool, bool);
virtual void OnServoShutdownComplete();
virtual void OnServoTitleChanged(winrt::hstring);
virtual void OnServoAlert(winrt::hstring);
virtual void OnServoURLChanged(winrt::hstring);
virtual void Flush();
virtual void MakeCurrent();
virtual bool OnServoAllowNavigation(winrt::hstring);
virtual void OnServoAnimatingChanged(bool);
private:
winrt::event<Windows::Foundation::EventHandler<hstring>> mOnURLChangedEvent;
winrt::event<Windows::Foundation::EventHandler<hstring>> mOnTitleChangedEvent;
winrt::event<HistoryChangedDelegate> mOnHistoryChangedEvent;
winrt::event<LoadStatusChangedDelegate> mOnLoadStartedEvent;
winrt::event<LoadStatusChangedDelegate> mOnLoadEndedEvent;
Windows::UI::Xaml::Controls::SwapChainPanel ServoControl::Panel();
void CreateRenderSurface();
void DestroyRenderSurface();
void RecoverFromLostDevice();
void StartRenderLoop();
void StopRenderLoop();
void Loop();
std::optional<Windows::Foundation::Uri> TryParseURI(hstring input) {
try {
return Windows::Foundation::Uri(input);
} catch (hresult_invalid_argument const &e) {
return {};
}
}
void
OnSurfaceClicked(IInspectable const &,
Windows::UI::Xaml::Input::PointerRoutedEventArgs const &);
void OnSurfaceManipulationDelta(
IInspectable const &,
Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs const &e);
template <typename Callable> void RunOnUIThread(Callable);
void RunOnGLThread(std::function<void()>);
std::unique_ptr<servo::Servo> mServo;
EGLSurface mRenderSurface{EGL_NO_SURFACE};
OpenGLES mOpenGLES;
bool mAnimating = false;
bool mLooping = false;
std::vector<std::function<void()>> mTasks;
CRITICAL_SECTION mGLLock;
CONDITION_VARIABLE mGLCondVar;
std::unique_ptr<Concurrency::task<void>> mLoopTask;
};
} // namespace winrt::ServoApp::implementation
namespace winrt::ServoApp::factory_implementation {
struct ServoControl
: ServoControlT<ServoControl, implementation::ServoControl> {};
} // namespace winrt::ServoApp::factory_implementation

View file

@ -0,0 +1,20 @@
namespace ServoApp {
delegate void LoadStatusChangedDelegate();
delegate void HistoryChangedDelegate(Boolean back, Boolean forward);
runtimeclass ServoControl : Windows.UI.Xaml.Controls.Control {
ServoControl();
void GoBack();
void GoForward();
void Reload();
void Stop();
Windows.Foundation.Uri LoadURIOrSearch(String url);
void Shutdown();
event LoadStatusChangedDelegate OnLoadStarted;
event LoadStatusChangedDelegate OnLoadEnded;
event HistoryChangedDelegate OnHistoryChanged;
event Windows.Foundation.EventHandler<String> OnTitleChanged;
event Windows.Foundation.EventHandler<String> OnURLChanged;
}
} // namespace ServoApp

View file

@ -0,0 +1,19 @@
<!-- \Themes\Generic.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ServoApp">
<Style TargetType="local:ServoControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ServoControl">
<Grid>
<SwapChainPanel x:Name="swapChainPanel" MinHeight="200" MinWidth="200" ManipulationMode="All">
</SwapChainPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>