Unverified Commit 2e33f236 authored by Andrey Azov's avatar Andrey Azov Committed by GitHub
Browse files

Browser messaging service (#108)

- use postMessage api instead of dispatching custom events on canvas wrapper element
- add actualChrLocation field to browser location state
  (shows where the browser is at any given moment)
- add message counter that keeps track of the sequence of messages
  exchanged between GenomeBrowser and BrowserChrome.
- move code for updating GenomeBrowser page url from Browser component to browserActions
- update .gitignore rules regarding .vscode folder
parent f91b94c1
Pipeline #27110 passed with stages
in 4 minutes and 28 seconds
......@@ -2,6 +2,7 @@
**/*.rs.bk
**/*.geany
*.geany
.vscode
node_modules/
dist/
......@@ -17,3 +18,6 @@ bundle-report.html
.env
localhost.crt
localhost.key
# Track .vscode/settings.json file
!.vscode/settings.json
use dom::domutil;
use dom::event::{
EventListener, EventControl, EventType, EventData,
ICustomEvent, Target, IMessageEvent
};
pub struct ReadyPingPongListener {
}
impl EventListener<()> for ReadyPingPongListener {
fn receive(&mut self, _el: &Target, e: &EventData, _idx: &()) {
match e {
EventData::MessageEvent(_,_,c) => {
let data = &c.data().unwrap();
if data["type"] == "bpane-ready-query" {
domutil::send_post_message("bpane-ready",&json!{{}});
}
},
_ => ()
};
}
}
pub fn activate() {
/* old-style activation. Harmless but can be removed when new protocol is in use on react end */
let body = domutil::query_selector_ok_doc("body","Cannot find body element");
domutil::add_attr(&body,"class","browser-app-ready");
domutil::remove_attr(&body.into(),"class","browser-app-not-ready");
/* new-style activation */
let pingpong = ReadyPingPongListener{};
let mut ec = EventControl::new(Box::new(pingpong),());
ec.add_event(EventType::MessageEvent);
domutil::send_post_message("bpane-ready",&json!{{}});
}
......@@ -11,7 +11,7 @@ use composit::{
};
use controller::input::{ Action, actions_run, startup_actions };
use controller::global::{ AppRunnerWeak, AppRunner };
use controller::output::{ Report, ViewportReport, ZMenuReports };
use controller::output::{ Report, ViewportReport, ZMenuReports, Counter };
use data::{ BackendConfig, BackendStickManager, HttpManager, HttpXferClerk, XferCache };
use debug::add_debug_sticks;
use dom::domutil;
......@@ -176,7 +176,7 @@ impl App {
pub fn run_actions(self: &mut App, evs: &Vec<Action>,currency: Option<f64>) {
if let Some(ref mut report) = self.report {
if currency.is_none() || report.is_current(currency.unwrap()) {
if currency.is_none() || self.with_counter(|c| c.is_current(currency.unwrap())) {
if self.action_backlog.len() > 0 {
console!("running backlog");
let backlog = self.action_backlog.drain(..).collect();
......@@ -186,8 +186,8 @@ impl App {
return;
}
}
console!("backlogging");
self.action_backlog.extend(evs.iter().cloned());
console!("backlogging to {:?}",self.action_backlog);
}
pub fn check_size(self: &mut App) {
......@@ -217,16 +217,8 @@ impl App {
self.printer.lock().unwrap().set_size(stage.get_size());
}
pub fn lock(&mut self) {
if let Some(ref mut report) = self.report {
report.lock();
}
}
pub fn unlock(&mut self) {
if let Some(ref mut report) = self.report {
report.unlock();
}
pub fn with_counter<F,G>(&mut self, cb: F) -> G where F: FnOnce(&mut Counter) -> G {
unwrap!(self.ar.upgrade()).with_counter(cb)
}
pub fn settle(&mut self) {
......
......@@ -2,14 +2,14 @@ use std::sync::{ Arc, Mutex, Weak };
use stdweb::web::HtmlElement;
use url::Url;
use dom::domutil;
use composit::register_compositor_ticks;
use controller::global::{ App, GlobalWeak };
use controller::scheduler::{ Scheduler, SchedRun, SchedulerGroup };
use controller::input::{
register_direct_events, register_user_events, register_dom_events
};
use controller::output::{ OutputAction, Report, ViewportReport, ZMenuReports };
use controller::output::{ OutputAction, Report, ViewportReport, ZMenuReports, Counter };
#[cfg(any(not(deploy),console))]
use data::blackbox::{
......@@ -25,6 +25,7 @@ use tácode::Tácode;
struct AppRunnerImpl {
g: GlobalWeak,
counter: Counter,
el: HtmlElement,
bling: Box<Bling>,
app: Arc<Mutex<App>>,
......@@ -60,9 +61,14 @@ impl AppRunner {
let g = unwrap!(g.clone().upgrade()).clone();
g.scheduler().make_group()
};
let counter = {
let g = unwrap!(g.clone().upgrade()).clone();
g.counter()
};
let mut out = AppRunner(Arc::new(Mutex::new(AppRunnerImpl {
g: g.clone(),
el: el.clone(),
counter,
bling,
app: Arc::new(Mutex::new(st)),
controls: Vec::<Box<EventControl<()>>>::new(),
......@@ -92,10 +98,18 @@ impl AppRunner {
out
}
pub fn with_counter<F,G>(&self, cb: F) -> G where F: FnOnce(&mut Counter) -> G {
cb(&mut self.0.lock().unwrap().counter)
}
pub fn get_browser_el(&mut self) -> HtmlElement {
self.0.lock().unwrap().browser_el.clone()
}
pub fn get_el(&self) -> HtmlElement {
self.0.lock().unwrap().el.clone()
}
pub fn add_timer<F>(&mut self, name: &str, mut cb: F, prio: usize)
where F: FnMut(&mut App, f64, &mut SchedRun) -> Vec<OutputAction> + 'static {
let mut ar = self.clone();
......@@ -122,7 +136,6 @@ impl AppRunner {
{
let el = self.0.lock().unwrap().el.clone();
register_user_events(self,&el);
register_direct_events(self,&el);
register_dom_events(self,&el);
}
......@@ -190,10 +203,15 @@ impl AppRunner {
}
pub fn bling_key(&mut self, key: &str) {
let mut imp = self.0.lock().unwrap();
let mut imp = self.0.lock().unwrap();
let app = imp.app.clone();
imp.bling.key(&app,key);
}
pub fn find_app(&mut self, el: &HtmlElement) -> bool {
let mut imp = self.0.lock().unwrap();
domutil::ancestor(el,&imp.el) || domutil::ancestor(&imp.el,el)
}
}
impl AppRunnerWeak {
......@@ -209,8 +227,3 @@ impl AppRunnerWeak {
pub fn none() -> AppRunnerWeak { AppRunnerWeak(Weak::new()) }
}
impl Drop for AppRunner {
fn drop(&mut self) {
console!("App runner dropped");
}
}
......@@ -23,50 +23,6 @@ use debug::{ DebugBling, create_interactors };
use dom::{ Bling, NoBling };
use dom::event::{ EventListener, Target, EventData, EventType, EventControl, ICustomEvent };
#[derive(Clone)]
struct BootingMissed(Arc<Mutex<Vec<(String,JSONValue)>>>);
struct BootingEventListener {
missed: BootingMissed
}
impl BootingMissed {
fn new() -> BootingMissed {
BootingMissed(Arc::new(Mutex::new(Vec::<(String,JSONValue)>::new())))
}
fn add(&mut self, name: &str, details: JSONValue) {
let mut v = self.0.lock().unwrap();
v.push((name.to_string(),details));
}
fn run_missed(&mut self, app: &mut App) {
let mut v = self.0.lock().unwrap();
for (event,details) in v.drain(..) {
match &event[..] {
"bpane" => run_direct_events(app,&details),
_ => ()
};
}
}
}
impl BootingEventListener {
fn new(missed: &BootingMissed) -> BootingEventListener {
BootingEventListener {
missed: missed.clone()
}
}
}
impl EventListener<()> for BootingEventListener {
fn receive(&mut self, _el: &Target, e: &EventData, _idx: &()) {
if let EventData::CustomEvent(_,_,_,c) = e {
self.missed.add(&c.event_type(),c.details().unwrap());
}
}
}
pub struct Booting {
global: Global,
http_manager: HttpManager,
......@@ -74,15 +30,11 @@ pub struct Booting {
el: HtmlElement,
key: String,
debug: bool,
missed: BootingMissed,
ec: EventControl<()>
}
impl Booting {
pub fn new(g: &mut Global, http_manager: &HttpManager, config_url: &Url,
el: &HtmlElement, key: &str, debug: bool) -> Booting {
let missed = BootingMissed::new();
let bel = BootingEventListener::new(&missed);
let mut out = Booting {
global: g.clone(),
http_manager: http_manager.clone(),
......@@ -90,11 +42,7 @@ impl Booting {
el: el.clone(),
key: key.to_string(),
debug,
missed: missed.clone(),
ec: EventControl::new(Box::new(bel),())
};
out.ec.add_event(EventType::CustomEvent("bpane".to_string()));
out.ec.add_element(&el.clone().into(),());
out
}
......@@ -144,7 +92,6 @@ impl Booting {
}
let app = ar.clone().state();
app.lock().unwrap().run_actions(&initial_actions(),None);
self.ec.reset();
self.missed.run_missed(&mut app.lock().unwrap());
console!("booted");
}
}
......@@ -10,14 +10,17 @@ use url::Url;
use util::{ set_instance_id, get_instance_id };
use controller::input::{
register_startup_events, register_shutdown_events
register_startup_events, register_shutdown_events, register_direct_events
};
use controller::global::{ AppRunner, Booting };
use controller::output::Counter;
use controller::scheduler::{ Scheduler, SchedulerGroup };
use data::{ BackendConfigBootstrap, HttpManager };
use dom::domutil;
use dom::domutil::browser_time;
use super::activate::activate;
const SCHEDULER_ALLOC : f64 = 12.; /* ms per raf */
pub struct GlobalImpl {
......@@ -25,7 +28,9 @@ pub struct GlobalImpl {
app_runners: HashMap<String,AppRunner>,
http_manager: HttpManager,
scheduler: Scheduler,
sched_group: SchedulerGroup
sched_group: SchedulerGroup,
counter: Counter,
ar_init: Vec<Box<FnMut(&AppRunner)>>
}
impl GlobalImpl {
......@@ -34,11 +39,13 @@ impl GlobalImpl {
let sched_group = scheduler.make_group();
set_instance_id();
let mut out = GlobalImpl {
counter: Counter::new(),
inst_id: get_instance_id(),
app_runners: HashMap::<String,AppRunner>::new(),
app_runners: HashMap::new(),
http_manager: HttpManager::new(),
scheduler,
sched_group
sched_group,
ar_init: Vec::new()
};
out.init();
out
......@@ -54,6 +61,10 @@ impl GlobalImpl {
}),3,false);
}
pub fn counter(&self) -> Counter {
self.counter.clone()
}
pub fn get_instance_id(&self) -> &str { &self.inst_id }
pub fn scheduler(&self) -> Scheduler {
......@@ -72,9 +83,35 @@ impl GlobalImpl {
}
}
pub fn register_app(&mut self, key: &str, app_runner: AppRunner) {
pub fn register_ar_init(&mut self, mut cb: Box<FnMut(&AppRunner)>) {
for ar in self.app_runners.values_mut() {
cb(&ar.clone());
}
self.ar_init.push(cb);
}
pub fn register_app(&mut self, key: &str, mut app_runner: AppRunner) {
for ari in &mut self.ar_init {
ari(&app_runner.clone());
}
self.app_runners.insert(key.to_string(),app_runner);
}
pub fn find_app(&mut self, el: &HtmlElement) -> Option<AppRunner> {
for ar in self.app_runners.values_mut() {
if ar.find_app(el) {
return Some(ar.clone())
}
}
return None
}
pub fn any_app(&mut self) -> Option<AppRunner> {
for ar in self.app_runners.values_mut() {
return Some(ar.clone());
}
None
}
}
#[derive(Clone)]
......@@ -103,11 +140,23 @@ impl Global {
self.0.borrow().scheduler()
}
pub fn counter(&self) -> Counter {
self.0.borrow().counter()
}
/* app registration */
fn unregister_app(&mut self, key: &str) {
self.0.borrow_mut().unregister_app(key);
}
pub fn find_app(&mut self, el: &HtmlElement) -> Option<AppRunner> {
self.0.borrow_mut().find_app(el)
}
pub fn any_app(&mut self) -> Option<AppRunner> {
self.0.borrow_mut().any_app()
}
pub fn trigger_app(&mut self, key: &str, el: &HtmlElement, debug: bool, config_url: &Url) {
self.unregister_app(key);
let http_manager = &self.0.borrow().http_manager.clone();
......@@ -126,6 +175,10 @@ impl Global {
self.0.borrow_mut().register_app(key,ar);
}
pub fn register_ar_init(&mut self, mut cb: Box<FnMut(&AppRunner)>) {
self.0.borrow_mut().register_ar_init(cb);
}
/* destruction */
pub fn destroy(&mut self) {
self.0.borrow_mut().destroy();
......@@ -147,9 +200,14 @@ impl GlobalWeak {
pub fn setup_global() {
/* setup */
Global::new();
let mut g = Global::new();
/* mark as ready */
let body = domutil::query_selector_ok_doc("body","Cannot find body element");
domutil::add_attr(&body,"class","browser-app-ready");
domutil::remove_attr(&body.into(),"class","browser-app-not-ready");
let mut eqm = register_direct_events(&g);
let mut eqm2 = eqm.clone();
g.register_ar_init(Box::new(move |ar| eqm.register_ar(&ar)));
/* setup ping/pong */
activate();
}
mod activate;
mod booting;
mod global;
mod apprunner;
......
......@@ -27,6 +27,24 @@ impl Action {
_ => true
}
}
fn order(&self) -> i32 {
match self {
Action::Noop => 0,
Action::AddComponent(_) => 1,
Action::SetState(_,_) => 2,
Action::SetStick(_) => 3,
Action::Resize(_) => 5,
Action::Pos(_,_) => 10,
Action::PosRange(_,_,_) => 10,
Action::ZoomTo(_) => 10,
Action::Move(_) => 10,
Action::Zoom(_) => 10,
Action::ZMenu(_) => 25,
Action::ShowZMenu(_,_,_) => 25,
Action::Settled => 30,
}
}
}
fn exe_pos_event(app: &App, v: Dot<f64,f64>, prop: Option<f64>) {
......@@ -142,7 +160,9 @@ fn exe_zmenu_show(a: &mut App, id: &str, pos: Dot<i32,i32>, payload: JSONValue)
}
pub fn actions_run(cg: &mut App, evs: &Vec<Action>, currency: Option<f64>) {
cg.lock();
cg.with_counter(|c| c.lock());
let mut evs = evs.to_vec();
evs.sort_by_key(|e| e.order());
for ev in evs {
let ev = ev.clone();
if ev.active() {
......@@ -164,7 +184,7 @@ pub fn actions_run(cg: &mut App, evs: &Vec<Action>, currency: Option<f64>) {
Action::Noop => ()
}
}
cg.unlock();
cg.with_counter(|c| c.unlock());
}
pub fn startup_actions() -> Vec<Action> {
......
use std::convert::TryInto;
use stdweb::unstable::TryInto;
use std::sync::{ Arc, Mutex };
use serde_json::from_str;
......@@ -7,13 +6,16 @@ use serde_json::Value as JSONValue;
use serde_json::Number as JSONNumber;
use stdweb::web::{ Element, HtmlElement };
use controller::global::{ App, AppRunner };
use controller::global::{ App, AppRunner, Global, GlobalWeak };
use controller::input::{ actions_run, Action };
use dom::event::{
EventListener, EventControl, EventType, EventData,
ICustomEvent, Target
ICustomEvent, Target, IMessageEvent
};
use dom::domutil;
use types::{ Move, Distance, Units };
use super::eventutil::{ extract_element, parse_message };
use super::eventqueue::EventQueueManager;
fn custom_movement_event(dir: &str, unit: &str, v: &JSONValue) -> Action {
if let JSONValue::Number(quant) = v {
......@@ -131,37 +133,76 @@ fn extract_counter(j: &JSONValue) -> Option<f64> {
}
}
pub fn run_direct_events(app: &mut App, j: &JSONValue) {
pub fn run_direct_events(app: &mut App, name: &str, j: &JSONValue) {
let evs = custom_make_events(&j);
console!("receive/A {}",j.to_string());
console!("receive/A {} {}",name,j.to_string());
console!("receive/B {:?}",evs);
app.run_actions(&evs,extract_counter(&j));
}
pub struct DirectEventListener {
cg: Arc<Mutex<App>>,
gw: GlobalWeak,
eqm: EventQueueManager
}
impl DirectEventListener {
pub fn new(cg: &Arc<Mutex<App>>) -> DirectEventListener {
DirectEventListener { cg: cg.clone() }
pub fn new(gw: GlobalWeak,eqm: &EventQueueManager) -> DirectEventListener {
DirectEventListener { gw, eqm: eqm.clone() }
}
pub fn run_direct(&mut self, el: &Element, name: &str, j: &JSONValue) {
console!("receive/C {:?} {}",el,j.to_string());
if let Some(mut g) = self.gw.upgrade() {
let el : Result<HtmlElement,_> = el.clone().try_into();
let el : Option<HtmlElement> = el.ok();
if let Some(el) = el {
if let Some(ar) = g.find_app(&el) {
let mut app = ar.state();
run_direct_events(&mut app.lock().unwrap(),name,j);
}
}
}
}
}
impl EventListener<()> for DirectEventListener {
fn receive(&mut self, _el: &Target, e: &EventData, _idx: &()) {
if let EventData::CustomEvent(_,_,_,c) = e {
run_direct_events(&mut self.cg.lock().unwrap(),
&c.details().unwrap());
match e {
EventData::CustomEvent(_,ec,name,c) => {
let details = c.details().unwrap();
let el = extract_element(&details,Some(ec.target().clone()));
if let Some(el) = el {
self.run_direct(&el.into(),name,&details);
} else {
console!("bpane sent to unknown app (event)");
}
},
EventData::MessageEvent(_,ec,c) => {
let data = c.data().unwrap();
if let Some(payload) = parse_message("bpane",&data) {
console!("receive/D {}",payload);
let sel = payload.get("selector").map(|v| v.as_str().unwrap());
if payload.get("_outgoing").is_some() {
return;
}
let ev = custom_make_events(&payload);
let currency = extract_counter(&payload);
self.eqm.add_by_selector(sel,&ev,currency);
}
},
_ => ()
}
}
}
pub fn register_direct_events(gc: &mut AppRunner, el: &HtmlElement) {
let elel : Element = el.clone().into();
let dlr = DirectEventListener::new(&gc.state());
pub fn register_direct_events(g: &Global) -> EventQueueManager {
let gw = GlobalWeak::new(g);
let eqm = EventQueueManager::new();
let dlr = DirectEventListener::new(gw,&eqm);
let mut ec = EventControl::new(Box::new(dlr),());
ec.add_event(EventType::CustomEvent("bpane".to_string()));
ec.add_element(&elel,());
gc.add_control(Box::new(ec));