morph
0.1.0
Asynchronous bridge between GUI thread and business models
Toggle main menu visibility
Loading...
Searching...
No Matches
browser_history.hpp
Go to the documentation of this file.
1
// SPDX-License-Identifier: Apache-2.0
2
#pragma once
3
4
/// @file
5
/// @brief `BrowserHistory`: the browser's session history as an `ui::IHistory`, on WebAssembly only (spec 3 §10).
6
///
7
/// Specified in `docs/spec/ui/host.md`, "Route history".
8
9
#ifdef __EMSCRIPTEN__
10
11
#include <emscripten/val.h>
12
13
#include <QObject>
14
#include <QTimer>
15
#include <chrono>
16
#include <functional>
17
#include <string>
18
#include <utility>
19
20
#include "
../ui/route_history.hpp
"
21
22
namespace
morph::qt_quick {
23
24
/// @brief The page's history: each entry's route is its URL fragment, `#/<route>`.
25
///
26
/// `push` and `replace` go through `history.pushState` and `history.replaceState`, which change the fragment without
27
/// a `hashchange`. A move back or forward, or an address the user edited, changes the fragment from outside; it is
28
/// found by reading `location.hash` on a timer on the Qt event loop, so `onPop`'s callback runs there, never inside
29
/// a JavaScript event handler. Reading it costs one property read per tick; a `popstate` listener would need a
30
/// JavaScript function bound to C++, which a header cannot register.
31
class
BrowserHistory final :
public
ui::IHistory {
32
public
:
33
/// @brief The page's history, read every @p interval.
34
/// @param interval How often the fragment is read.
35
explicit
BrowserHistory(std::chrono::milliseconds interval = std::chrono::milliseconds{100}) : _seen{hash()} {
36
_timer.setInterval(interval);
37
QObject::connect(&_timer, &QTimer::timeout, [
this
] { poll(); });
38
_timer.start();
39
}
40
41
[[nodiscard]] std::string current()
const override
{
return
ui::routeOfFragment(hash()); }
42
43
void
push(std::string
const
& route)
override
{ write(
"pushState"
, route); }
44
45
void
replace(std::string
const
& route)
override
{ write(
"replaceState"
, route); }
46
47
void
onPop(std::function<
void
(std::string
const
& route)> popped)
override
{ _popped = std::move(popped); }
48
49
private
:
50
[[nodiscard]]
static
std::string hash() {
return
emscripten::val::global(
"location"
)[
"hash"
].as<std::string>(); }
51
52
// Skipped while the address holds a move the timer has not reported yet: the user's move wins, and the poll
53
// reports it.
54
void
write(
char
const
* method, std::string
const
& route) {
55
if
(hash() != _seen) {
56
return
;
57
}
58
emscripten::val::global(
"history"
).call<
void
>(method, emscripten::val::null(), std::string{},
59
ui::fragmentOf(route));
60
_seen = hash();
61
}
62
63
// A copy of the callback: it may replace it while it runs.
64
void
poll() {
65
std::string now = hash();
66
if
(now == _seen) {
67
return
;
68
}
69
_seen = now;
70
if
(
auto
const
popped = _popped) {
71
popped(ui::routeOfFragment(now));
72
}
73
}
74
75
QTimer _timer;
76
std::string _seen;
77
std::function<void(std::string
const
&)> _popped;
78
};
79
80
}
// namespace morph::qt_quick
81
82
#endif
route_history.hpp
Route history (spec 3 §10, spec 6 §20): the route of the screen a shell shows, kept in a history the ...
morph
qt_quick
browser_history.hpp
Generated by
1.17.0