From faf87ce316700eff9a81d6b35000c9da76e9d2be Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Fri, 28 Jan 2022 09:56:56 +0800 Subject: [PATCH 001/103] feat: add `issue` template --- .github/ISSUE_TEMPLATE/desktop.md | 28 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 16 +++++++++++++ .github/ISSUE_TEMPLATE/web.md | 27 ++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/desktop.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/web.md diff --git a/.github/ISSUE_TEMPLATE/desktop.md b/.github/ISSUE_TEMPLATE/desktop.md new file mode 100644 index 000000000..cbc26136e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/desktop.md @@ -0,0 +1,28 @@ +--- +name: Desktop Platform Issue +about: The Problem with desktop platform +--- + +## Enviroment + +> development enviroment info. + +- system version: +- dioxus version: +- rust version: + +## Error Info + +> verbose error information. + +``` + +``` + +## Bad Code + +> the code where error occurred. + +```rust + +``` \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..f8735b9a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature Request +about: If you have any interesting advice, you can tell us. +--- + +## Specific Demand + + + +## Implement Suggestion + + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/web.md b/.github/ISSUE_TEMPLATE/web.md new file mode 100644 index 000000000..f92ec0b99 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/web.md @@ -0,0 +1,27 @@ +--- +name: Web(WASM) Platform Issue +about: The Problem with Web | WASM platform +--- + +## Enviroment + +> development enviroment info. + +- dioxus version: +- rust version: + +## Error Info + +> verbose error information. + +``` + +``` + +## Bad Code + +> the code where error occurred. + +```rust + +``` \ No newline at end of file From 6df36feb70c32d653438e8ed363924c6918cb2bf Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Fri, 28 Jan 2022 10:02:45 +0800 Subject: [PATCH 002/103] feat: add `issue` template --- .github/ISSUE_TEMPLATE/core_problem.md | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/core_problem.md diff --git a/.github/ISSUE_TEMPLATE/core_problem.md b/.github/ISSUE_TEMPLATE/core_problem.md new file mode 100644 index 000000000..0e7cb3390 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/core_problem.md @@ -0,0 +1,28 @@ +--- +name: Core Development Issue +about: Something that doesn't directly relate to the platform +--- + +## Enviroment + +> development enviroment info. + +- dioxus version: +- dioxus feature: ["web", "desktop", ..."] +- rust version: + +## Error Info + +> verbose error information. + +``` + +``` + +## Bad Code + +> the code where error occurred. + +```rust + +``` \ No newline at end of file From a21e7d4dd168129da06f535f9dc4b1de724617cb Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 12:00:13 -0500 Subject: [PATCH 003/103] fix: use_route should subscribe to changes to the route --- packages/router/src/hooks/use_route.rs | 32 ++++++++++++++++++++------ packages/router/src/service.rs | 14 ++++++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 97dd6c048..ab6fad268 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,4 +1,4 @@ -use dioxus_core::ScopeState; +use dioxus_core::{ScopeId, ScopeState}; use gloo::history::{HistoryResult, Location}; use serde::de::DeserializeOwned; use std::{rc::Rc, str::FromStr}; @@ -74,10 +74,28 @@ impl UseRoute { /// This hook provides access to information about the current location in the /// context of a [`Router`]. If this function is called outside of a `Router` /// component it will panic. -pub fn use_route(cx: &ScopeState) -> UseRoute { - let router = cx - .consume_context::() - .expect("Cannot call use_route outside the scope of a Router component") - .clone(); - UseRoute { router } +pub fn use_route(cx: &ScopeState) -> &UseRoute { + &cx.use_hook(|_| { + let router = cx + .consume_context::() + .expect("Cannot call use_route outside the scope of a Router component"); + + router.subscribe_onchange(cx.scope_id()); + + UseRouteInner { + router: UseRoute { router }, + scope: cx.scope_id(), + } + }) + .router +} + +struct UseRouteInner { + router: UseRoute, + scope: ScopeId, +} +impl Drop for UseRouteInner { + fn drop(&mut self) { + self.router.router.unsubscribe_onchange(self.scope) + } } diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 9da2bef7e..f2682f1f0 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -1,7 +1,7 @@ use gloo::history::{BrowserHistory, History, HistoryListener, Location}; use std::{ cell::{Cell, Ref, RefCell}, - collections::HashMap, + collections::{HashMap, HashSet}, rc::Rc, }; @@ -12,6 +12,7 @@ pub struct RouterService { pub(crate) pending_events: Rc>>, history: Rc>, slots: Rc>>, + onchange_listeners: Rc>>, root_found: Rc>>, cur_path_params: Rc>>, listener: HistoryListener, @@ -73,6 +74,7 @@ impl RouterService { regen_route, slots, pending_events, + onchange_listeners: Rc::new(RefCell::new(HashSet::new())), cur_path_params: Rc::new(RefCell::new(HashMap::new())), } } @@ -143,6 +145,16 @@ impl RouterService { pub fn current_path_params(&self) -> Ref> { self.cur_path_params.borrow() } + + pub fn subscribe_onchange(&self, id: ScopeId) { + log::trace!("Subscribing onchange for scope id {:?}", id); + self.onchange_listeners.borrow_mut().insert(id); + } + + pub fn unsubscribe_onchange(&self, id: ScopeId) { + log::trace!("Subscribing onchange for scope id {:?}", id); + self.onchange_listeners.borrow_mut().remove(&id); + } } fn clean_route(route: String) -> String { From 5ee9d6c4348a2f51adac827f715fb138918d1dc6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 12:04:16 -0500 Subject: [PATCH 004/103] fix: attach router listener to subscriber list --- packages/router/src/service.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index f2682f1f0..2378c5aee 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -43,6 +43,7 @@ impl RouterService { let location = history.location(); let path = location.path(); + let onchange_listeners = Rc::new(RefCell::new(HashSet::new())); let slots: Rc>> = Default::default(); let pending_events: Rc>> = Default::default(); let root_found = Rc::new(Cell::new(None)); @@ -52,6 +53,7 @@ impl RouterService { let regen_route = regen_route.clone(); let root_found = root_found.clone(); let slots = slots.clone(); + let onchange_listeners = onchange_listeners.clone(); move || { root_found.set(None); // checking if the route is valid is cheap, so we do it @@ -60,6 +62,13 @@ impl RouterService { regen_route(*slot); } + for listener in onchange_listeners.borrow_mut().iter() { + log::trace!("regenerating listener {:?}", listener); + regen_route(*listener); + } + + + // also regenerate the root regen_route(root_scope); @@ -74,7 +83,7 @@ impl RouterService { regen_route, slots, pending_events, - onchange_listeners: Rc::new(RefCell::new(HashSet::new())), + onchange_listeners, cur_path_params: Rc::new(RefCell::new(HashMap::new())), } } From 9da46eb7bc207997ca7779c58fcb2a9645dfa9d0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 12:07:10 -0500 Subject: [PATCH 005/103] chore: rustfmt --- packages/router/src/service.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 2378c5aee..b62bc78d5 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -67,8 +67,6 @@ impl RouterService { regen_route(*listener); } - - // also regenerate the root regen_route(root_scope); From 79e09934aa685d03d6e0b323723bc1cd537d74d9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 12:14:40 -0500 Subject: [PATCH 006/103] chore: add docs to router UseRouteListener --- packages/router/src/hooks/use_route.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index ab6fad268..3b609f5e2 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -8,6 +8,7 @@ use crate::RouterService; /// This struct provides is a wrapper around the internal router /// implementation, with methods for getting information about the current /// route. +#[derive(Clone)] pub struct UseRoute { router: Rc, } @@ -82,7 +83,7 @@ pub fn use_route(cx: &ScopeState) -> &UseRoute { router.subscribe_onchange(cx.scope_id()); - UseRouteInner { + UseRouteListener { router: UseRoute { router }, scope: cx.scope_id(), } @@ -90,11 +91,15 @@ pub fn use_route(cx: &ScopeState) -> &UseRoute { .router } -struct UseRouteInner { +// The entire purpose of this struct is to unubscribe this component when it is unmounted. +// The UseRoute can be cloned into async contexts, so we can't rely on its drop to unubscribe. +// Instead, we hide the drop implementation on this private type exclusive to the hook, +// and reveal our cached version of UseRoute to the component. +struct UseRouteListener { router: UseRoute, scope: ScopeId, } -impl Drop for UseRouteInner { +impl Drop for UseRouteListener { fn drop(&mut self) { self.router.router.unsubscribe_onchange(self.scope) } From f26accd685457ae880b97873f86c90e62640655a Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Sat, 29 Jan 2022 01:46:00 +0800 Subject: [PATCH 007/103] Delete core_problem.md --- .github/ISSUE_TEMPLATE/core_problem.md | 28 -------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/core_problem.md diff --git a/.github/ISSUE_TEMPLATE/core_problem.md b/.github/ISSUE_TEMPLATE/core_problem.md deleted file mode 100644 index 0e7cb3390..000000000 --- a/.github/ISSUE_TEMPLATE/core_problem.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Core Development Issue -about: Something that doesn't directly relate to the platform ---- - -## Enviroment - -> development enviroment info. - -- dioxus version: -- dioxus feature: ["web", "desktop", ..."] -- rust version: - -## Error Info - -> verbose error information. - -``` - -``` - -## Bad Code - -> the code where error occurred. - -```rust - -``` \ No newline at end of file From d00a371e1022bdf2cf8057a5fc112090dc2307d5 Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Sat, 29 Jan 2022 01:47:01 +0800 Subject: [PATCH 008/103] Delete desktop.md --- .github/ISSUE_TEMPLATE/desktop.md | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/desktop.md diff --git a/.github/ISSUE_TEMPLATE/desktop.md b/.github/ISSUE_TEMPLATE/desktop.md deleted file mode 100644 index cbc26136e..000000000 --- a/.github/ISSUE_TEMPLATE/desktop.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Desktop Platform Issue -about: The Problem with desktop platform ---- - -## Enviroment - -> development enviroment info. - -- system version: -- dioxus version: -- rust version: - -## Error Info - -> verbose error information. - -``` - -``` - -## Bad Code - -> the code where error occurred. - -```rust - -``` \ No newline at end of file From 265ecd5c8ee1eac5241f13c0e4231c4c4a662f4b Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Sat, 29 Jan 2022 01:47:07 +0800 Subject: [PATCH 009/103] Delete feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index f8735b9a2..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Feature Request -about: If you have any interesting advice, you can tell us. ---- - -## Specific Demand - - - -## Implement Suggestion - - \ No newline at end of file From 003f8be7a25e1f21c30eb5375915e2dc68e935ad Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Sat, 29 Jan 2022 02:14:22 +0800 Subject: [PATCH 010/103] feat: add `issue` template --- .github/ISSUE_TEMPLATE/bug_report.md | 36 ++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_requst.md | 16 +++++++++++ .github/ISSUE_TEMPLATE/web.md | 27 ------------------ 3 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_requst.md delete mode 100644 .github/ISSUE_TEMPLATE/web.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..e31219bb6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve Dioxus +--- + +**Problem** + + + +**Steps To Reproduce** + +Steps to reproduce the behavior: + +- +- +- + +**Expected behavior** + +A clear and concise description of what you expected to happen. + +**Screenshots** + +If applicable, add screenshots to help explain your problem. + +**Environment:** + - Dioxus version: [e.g. v0.17, `master`] + - Rust version: [e.g. 1.43.0, `nightly`] + - OS info: [e.g. MacOS] + - App platform: [e.g. `web`, `desktop`] + +**Questionnaire** + +- [ ] I'm interested in fixing this myself but don't know where to start +- [ ] I would like to fix and I have a solution +- [ ] I don't have time to fix this right now, but maybe later \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_requst.md b/.github/ISSUE_TEMPLATE/feature_requst.md new file mode 100644 index 000000000..f8735b9a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_requst.md @@ -0,0 +1,16 @@ +--- +name: Feature Request +about: If you have any interesting advice, you can tell us. +--- + +## Specific Demand + + + +## Implement Suggestion + + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/web.md b/.github/ISSUE_TEMPLATE/web.md deleted file mode 100644 index f92ec0b99..000000000 --- a/.github/ISSUE_TEMPLATE/web.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Web(WASM) Platform Issue -about: The Problem with Web | WASM platform ---- - -## Enviroment - -> development enviroment info. - -- dioxus version: -- rust version: - -## Error Info - -> verbose error information. - -``` - -``` - -## Bad Code - -> the code where error occurred. - -```rust - -``` \ No newline at end of file From e24957fc191476184816fe5dee249f691170d4ae Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 14:38:17 -0500 Subject: [PATCH 011/103] feat: enable use_router --- packages/router/src/hooks/use_route.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 3b609f5e2..31a157a8f 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -104,3 +104,11 @@ impl Drop for UseRouteListener { self.router.router.unsubscribe_onchange(self.scope) } } + +/// This hook provides access to the `RouterService` for the app. +pub fn use_router(cx: &ScopeState) -> &RouterService { + cx.use_hook(|_| { + cx.consume_context::() + .expect("Cannot call use_route outside the scope of a Router component") + }) +} From c092bd43edf1891c427722bc9ca04e11c9359069 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 16:12:06 -0500 Subject: [PATCH 012/103] fix: use_state --- packages/hooks/src/usestate.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index eb3900981..914587c13 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -61,6 +61,8 @@ pub fn use_state<'a, T: 'static>( } }); + hook.current_val = hook.slot.borrow().clone(); + (hook.current_val.as_ref(), hook) } From 72c6bb3d0b7253f084f7e3bcf55d458cb4adeedb Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Fri, 28 Jan 2022 15:31:43 -0600 Subject: [PATCH 013/103] Make log message in Link component trace level, not debug All the other routing-related messages are at the trace level. Leaving this at debug was an oversight on my part. --- packages/router/src/components/link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 4c0ecb996..fd8eea66d 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -41,7 +41,7 @@ pub struct LinkProps<'a> { } pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - log::debug!("render Link to {}", cx.props.to); + log::trace!("render Link to {}", cx.props.to); if let Some(service) = cx.consume_context::() { return cx.render(rsx! { a { From e9792e9b95521fcf0b0b4e240d7cf0397cd79b8f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 21:43:01 -0500 Subject: [PATCH 014/103] tests: add miri stress test --- packages/core/tests/miri_stress.rs | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index bb4963b71..6b7805261 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -313,3 +313,42 @@ fn leak_thru_children() { dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); } + +#[test] +fn test_pass_thru() { + #[inline_props] + fn Router<'a>(cx: Scope, children: Element<'a>) -> Element { + cx.render(rsx! { + &cx.props.children + }) + } + + fn MemoizedThing(cx: Scope) -> Element { + rsx!(cx, div { "memoized" }) + } + + fn app(cx: Scope) -> Element { + let thing = cx.use_hook(|_| "asd"); + rsx!(cx, + Router { + MemoizedThing { + } + } + ) + } + + let mut dom = new_dom(app, ()); + let _ = dom.rebuild(); + + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); +} From 05d68b1107677ad49b4e9aba1ef094b64c8d6262 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 21:45:40 -0500 Subject: [PATCH 015/103] ci: fewer windows targets this commit decreases the number of windows targets we check to speed up CI --- .github/workflows/windows.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 183fb282d..b776be433 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -19,13 +19,7 @@ jobs: max-parallel: 2 fail-fast: false matrix: - target: - [ - i686-pc-windows-gnu, - i686-pc-windows-msvc, - x86_64-pc-windows-gnu, - x86_64-pc-windows-msvc, - ] + target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc] cfg_release_channel: [stable] steps: From 66bc09cf2837d9efe794f13d5ba6118b91b2d8d6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 21:46:15 -0500 Subject: [PATCH 016/103] ci: remove i686 windows target --- .github/workflows/windows.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b776be433..2b6ea5c23 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,12 +41,6 @@ jobs: rustup target add ${{ matrix.target }} shell: powershell - - name: Add mingw32 to path for i686-gnu - run: | - echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH - if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly' - shell: bash - - name: Add mingw64 to path for x86_64-gnu run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly' From 56f3002aede54807c8f385a8e1eba88386946897 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:17:14 -0500 Subject: [PATCH 017/103] feat: add changelogs --- CHANGELOG.md | 20 +++++++ packages/core-macro/CHANGELOG.md | 40 +++++++++++++- packages/core/CHANGELOG.md | 57 ++++++++++++++++++- packages/desktop/CHANGELOG.md | 94 ++++++++++++++++++++++++++++++-- packages/hooks/CHANGELOG.md | 36 +++++++++++- packages/html/CHANGELOG.md | 35 +++++++++++- packages/mobile/CHANGELOG.md | 23 +++++++- packages/router/CHANGELOG.md | 58 +++++++++++++++++++- packages/ssr/CHANGELOG.md | 31 ++++++++++- packages/web/CHANGELOG.md | 60 +++++++++++++++++++- 10 files changed, 437 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b624794..b056a9590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Commit Statistics + + + + - 1 commit contributed to the release over the course of 7 calendar days. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) +
+ ## v0.1.7 (2022-01-08) ### Bug Fixes diff --git a/packages/core-macro/CHANGELOG.md b/packages/core-macro/CHANGELOG.md index 79cbe766f..cf1143e1e 100644 --- a/packages/core-macro/CHANGELOG.md +++ b/packages/core-macro/CHANGELOG.md @@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - flatten props attrs + - add "optional" flag for props + +### Bug Fixes + + - detection of f-string formatting in components + +### Commit Statistics + + + + - 9 commits contributed to the release over the course of 12 calendar days. + - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471)) + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) + - flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d)) + - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f)) + - detection of f-string formatting in components ([`90abd9c`](https://github.comgit//DioxusLabs/dioxus/commit/90abd9c9a08c165384fae9c1f7c3fd098d512c48)) + - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11)) + - Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58)) + - Merge pull request #127 from DioxusLabs/jk/handler-tweak ([`5bce294`](https://github.comgit//DioxusLabs/dioxus/commit/5bce294a86941090660b1cfd87809a2b0b76d2ce)) + - add "optional" flag for props ([`47bc4e4`](https://github.comgit//DioxusLabs/dioxus/commit/47bc4e4a44a7d08d4f42102d13f0766d9d6bf358)) +
+ ## v0.1.6 (2022-01-08) ### Documentation @@ -64,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 138 commits contributed to the release over the course of 352 calendar days. + - 139 commits contributed to the release over the course of 352 calendar days. - 123 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -75,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release dioxus-core-macro v0.1.6 ([`db0a5bd`](https://github.comgit//DioxusLabs/dioxus/commit/db0a5bd6ec93803cddb3c6fda4172257b9c301c6)) - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76)) - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee)) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index a0aa9f28d..563106db3 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -5,6 +5,58 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Documentation + + - add comments for the Handler + +### New Features + + - add "spawn" method + - allow providing context to the root component + +### Bug Fixes + + - provide_root_context on root scopes + - proprogation of root context + - allow eventhandler to derive default + +### Commit Statistics + + + + - 18 commits contributed to the release over the course of 13 calendar days. + - 8 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a)) + - Merge branch 'master' of github.com:DioxusLabs/dioxus ([`8899701`](https://github.comgit//DioxusLabs/dioxus/commit/88997019c5c74658bfd76344d6bb6f3565943b41)) + - add miri stress test ([`e9792e9`](https://github.comgit//DioxusLabs/dioxus/commit/e9792e9b95521fcf0b0b4e240d7cf0397cd79b8f)) + - add "spawn" method ([`bad4b77`](https://github.comgit//DioxusLabs/dioxus/commit/bad4b773b7bbab6a38d4f48d88d8c5a8787927ac)) + - drop hooks before resetting bump arena ([`2e4f765`](https://github.comgit//DioxusLabs/dioxus/commit/2e4f7659327b69b74e20af59b6d5e65c699b6adb)) + - provide_root_context on root scopes ([`d9a07dd`](https://github.comgit//DioxusLabs/dioxus/commit/d9a07ddddb45ba00e35645ac0e0ad1a4d2dd996d)) + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) + - Merge pull request #121 from DioxusLabs/jk/unify ([`b287a4c`](https://github.comgit//DioxusLabs/dioxus/commit/b287a4cab3eec9e6961a3f010846291f4f105747)) + - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f)) + - proprogation of root context ([`c8d528b`](https://github.comgit//DioxusLabs/dioxus/commit/c8d528b3b18e320ca0cee1542ae5fc659c1cca81)) + - allow providing context to the root component ([`d2bd175`](https://github.comgit//DioxusLabs/dioxus/commit/d2bd1751436ef4bec554bb361ba87aea1357036a)) + - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11)) + - Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58)) + - Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97)) + - Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f)) + - Don't expect all components to have a scope in ScopeArena.ensure_drop_safety ([`9b282d8`](https://github.comgit//DioxusLabs/dioxus/commit/9b282d877ba0fe2463687cde7ea899b7f8510425)) + - add comments for the Handler ([`036a0ff`](https://github.comgit//DioxusLabs/dioxus/commit/036a0ff49a7dade0e04c9c07071a1ff49133ee24)) + - allow eventhandler to derive default ([`e47ead5`](https://github.comgit//DioxusLabs/dioxus/commit/e47ead5347ce778935f8f2127fcb14f520eda0ce)) +
+ ## v0.1.7 (2022-01-08) ### Documentation @@ -138,7 +190,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 437 commits contributed to the release over the course of 358 calendar days. + - 438 commits contributed to the release over the course of 358 calendar days. - 416 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -149,8 +201,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** - - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) + - Release dioxus-core v0.1.7 ([`16d73b2`](https://github.comgit//DioxusLabs/dioxus/commit/16d73b240fc446c6f6996c19ccf52bbcda2eca79)) - add title to doc comment ([`d11f322`](https://github.comgit//DioxusLabs/dioxus/commit/d11f322f554e7dbf43b988c9cfda56498cc49872)) + - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - better document the `EventHandler` type ([`be9f1a5`](https://github.comgit//DioxusLabs/dioxus/commit/be9f1a52ad2b04f101397ae34482ea7394df653b)) - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76)) - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee)) diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index 245d05623..65657ab37 100644 --- a/packages/desktop/CHANGELOG.md +++ b/packages/desktop/CHANGELOG.md @@ -5,6 +5,76 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Documentation + + - fix web doc example and use &mut for builders everywhere + +### New Features + + - default asset server + - remove dioxus id on non-event elements + - also hide placeholder node + - wire up both desktop and web + - bool attr white list + - setup a typescript build + +### Bug Fixes + + - custom protocol receiver type + - clippy + - wry pathing + - cursor jumping and use set instead of lsit + - format code + - check `href` null + - add exclusion list + - check `href` null + - prevent `submit` default + +### Commit Statistics + + + + - 26 commits contributed to the release over the course of 11 calendar days. + - 17 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - custom protocol receiver type ([`22308eb`](https://github.comgit//DioxusLabs/dioxus/commit/22308eb26a9ea48b14f5f5abb833aa90a4e3fc40)) + - default asset server ([`430cde7`](https://github.comgit//DioxusLabs/dioxus/commit/430cde7068d308f2783e33d278fd0c0efa659c1b)) + - fix web doc example and use &mut for builders everywhere ([`a239d2b`](https://github.comgit//DioxusLabs/dioxus/commit/a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63)) + - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3)) + - clippy ([`6bc45b1`](https://github.comgit//DioxusLabs/dioxus/commit/6bc45b1c5064a4e2b04d452c52a8167ad179691e)) + - Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401)) + - wry pathing ([`bad3616`](https://github.comgit//DioxusLabs/dioxus/commit/bad36162af764291f5a031b6233d151f61d745a4)) + - remove dioxus id on non-event elements ([`95e93ed`](https://github.comgit//DioxusLabs/dioxus/commit/95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36)) + - also hide placeholder node ([`eb13884`](https://github.comgit//DioxusLabs/dioxus/commit/eb138848ec7a8978f0ed7c717374684d2315dc03)) + - drag and drop support ([`9ae981a`](https://github.comgit//DioxusLabs/dioxus/commit/9ae981a1af4b5474ce16e27e070794d59128c12a)) + - feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9)) + - cursor jumping and use set instead of lsit ([`be614e6`](https://github.comgit//DioxusLabs/dioxus/commit/be614e6535e6e13e6ff93e9c6a171c1c002e6b01)) + - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f)) + - feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3)) + - Merge pull request #101 from alexkirsz/ci ([`29bf424`](https://github.comgit//DioxusLabs/dioxus/commit/29bf424b0976b95ff645bb128d0e758cf0186614)) + - Merge pull request #139 from DioxusLabs/jk/provide-context-any ([`70f2ef4`](https://github.comgit//DioxusLabs/dioxus/commit/70f2ef43db5b6737bd9bcbfc1aa21c834ce4b395)) + - Merge branch 'master' into jk/unify ([`824defa`](https://github.comgit//DioxusLabs/dioxus/commit/824defa2dbcc16d66588b3976699d89b65a8a068)) + - wire up both desktop and web ([`05331dd`](https://github.comgit//DioxusLabs/dioxus/commit/05331ddd8033f6997d4916179b62f4d62f832988)) + - format code ([`9256161`](https://github.comgit//DioxusLabs/dioxus/commit/92561612c727e73356d7d36e16af39aacf02a56d)) + - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11)) + - bool attr white list ([`8f4aa84`](https://github.comgit//DioxusLabs/dioxus/commit/8f4aa84f1a4f2443b34d81ee42490564e168de53)) + - setup a typescript build ([`5bf6c96`](https://github.comgit//DioxusLabs/dioxus/commit/5bf6c96f9fed04de949403202bafcbeadb5d2030)) + - check `href` null ([`2073b40`](https://github.comgit//DioxusLabs/dioxus/commit/2073b400df55f0c6d8bed7371b2313be6c064e6e)) + - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b)) + - check `href` null ([`327f901`](https://github.comgit//DioxusLabs/dioxus/commit/327f9015481809d8e5b9e69f26202e8d66dd198e)) + - prevent `submit` default ([`8089023`](https://github.comgit//DioxusLabs/dioxus/commit/8089023a6c3a54957af9c9c05c9dee6088b059ef)) +
+ ## v0.1.5 (2022-01-08) ### Documentation @@ -37,6 +107,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - omg what a dumb mistake - bless up, no more segfaults - wire up event delegator for webview + - link open in browser + - move `rpc` to handler + - `open_browser` bool attribute + - link open in browser + - link open in browser ### Bug Fixes @@ -48,13 +123,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - tags - desktop and mobile - messed up how lifetimes worked, need to render once per component + - error pattern + - format code ### Commit Statistics - - 91 commits contributed to the release over the course of 151 calendar days. - - 78 commits where understood as [conventional](https://www.conventionalcommits.org). + - 98 commits contributed to the release over the course of 151 calendar days. + - 84 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -64,11 +141,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** - - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) - - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b)) + - Release dioxus-desktop v0.1.5 ([`cd0dcac`](https://github.comgit//DioxusLabs/dioxus/commit/cd0dcacaf2862f26d29acb21d98f75d41b940e3f)) - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a)) + - link open in browser ([`46fd6ac`](https://github.comgit//DioxusLabs/dioxus/commit/46fd6ac3450ca5ebf9aecb2d59a5a92b2a68bdd0)) + - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) + - error pattern ([`62b637f`](https://github.comgit//DioxusLabs/dioxus/commit/62b637f8b0eaf616c49461fa23b9251a79abc147)) + - move `rpc` to handler ([`f006f50`](https://github.comgit//DioxusLabs/dioxus/commit/f006f50317f4b75fac353bc988db057a281ba7f8)) + - `open_browser` bool attribute ([`9e04ce5`](https://github.comgit//DioxusLabs/dioxus/commit/9e04ce5342850d2e0a01dde169807d6f6eb16566)) + - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) + - format code ([`5233ee9`](https://github.comgit//DioxusLabs/dioxus/commit/5233ee97d9314f7f0e0bdf05c56d2a9e4201a596)) + - link open in browser ([`c737c42`](https://github.comgit//DioxusLabs/dioxus/commit/c737c424b05ad8453e8770a14a0d210fb0c7c2fe)) - Merge pull request #89 from DioxusLabs/jk/simplify-example-run ([`8b6aa8b`](https://github.comgit//DioxusLabs/dioxus/commit/8b6aa8b880b6cb5c95e0c0743aad4e4e74388e05)) + - link open in browser ([`a0f6015`](https://github.comgit//DioxusLabs/dioxus/commit/a0f60152bc7e5866f114ed469809ce8be70d17d4)) - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee)) - Merge pull request #80 from DioxusLabs/jk/router2dotoh ([`cdc2d8e`](https://github.comgit//DioxusLabs/dioxus/commit/cdc2d8ec6d123245c2ea5f6d10af02b6a6833994)) - clear warnigns ([`175a6a1`](https://github.comgit//DioxusLabs/dioxus/commit/175a6a199c6738d8d0c7646ba0ec3fc4406c6535)) diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 07e15ed64..253768068 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Bug Fixes + + - use_state + - exampels + +### Commit Statistics + + + + - 9 commits contributed to the release over the course of 21 calendar days. + - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a)) + - Merge pull request #158 from DioxusLabs/jk/router-onchange ([`08988e1`](https://github.comgit//DioxusLabs/dioxus/commit/08988e1bfec35eadb30e50b5d677dbb36af91b9b)) + - use_state ([`c092bd4`](https://github.comgit//DioxusLabs/dioxus/commit/c092bd43edf1891c427722bc9ca04e11c9359069)) + - exampels ([`a8952a9`](https://github.comgit//DioxusLabs/dioxus/commit/a8952a9ee8d8831fa911cef4e7035293c3a9f88b)) + - Merge branch 'master' into jk/update-hooks ([`5c4bd08`](https://github.comgit//DioxusLabs/dioxus/commit/5c4bd0881bc10572440d9272ceb8ca774431c406)) + - modify usestate to be borrowed ([`58839f4`](https://github.comgit//DioxusLabs/dioxus/commit/58839f47bae3c49cadd4b41da9a5debebb1def99)) + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) + - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f)) + - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11)) +
+ ## v0.1.6 (2022-01-08) ### Documentation @@ -52,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 93 commits contributed to the release over the course of 358 calendar days. + - 94 commits contributed to the release over the course of 358 calendar days. - 82 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -63,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49)) - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76)) diff --git a/packages/html/CHANGELOG.md b/packages/html/CHANGELOG.md index 9648c0c8f..48c7f622c 100644 --- a/packages/html/CHANGELOG.md +++ b/packages/html/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Bug Fixes + + - rustfmt + +### Commit Statistics + + + + - 7 commits contributed to the release over the course of 19 calendar days. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - rustfmt ([`43e78d5`](https://github.comgit//DioxusLabs/dioxus/commit/43e78d56f72e23ada939f5688a6bd2fdd9b8292d)) + - Add gap and row_gap to style_trait_methods ([`5f4a724`](https://github.comgit//DioxusLabs/dioxus/commit/5f4a72446e63441628e18bb19fd9b2d9f65f9d52)) + - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3)) + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) + - Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401)) + - feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9)) + - feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3)) +
+ ## v0.1.4 (2022-01-08) ### Documentation @@ -42,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 60 commits contributed to the release over the course of 184 calendar days. + - 61 commits contributed to the release over the course of 184 calendar days. - 52 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -53,8 +83,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** - - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) + - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) - enable prevent_default everywhere ([`9dff700`](https://github.comgit//DioxusLabs/dioxus/commit/9dff700c220dd9e0da2ee028900e82fd24f9d0dd)) + - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76)) - memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c)) - new versions of everything ([`4ea5c99`](https://github.comgit//DioxusLabs/dioxus/commit/4ea5c990d72b1645724ab0a88ffea2baf28e2835)) diff --git a/packages/mobile/CHANGELOG.md b/packages/mobile/CHANGELOG.md index 284068810..4486eb279 100644 --- a/packages/mobile/CHANGELOG.md +++ b/packages/mobile/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Commit Statistics + + + + - 1 commit contributed to the release over the course of 7 calendar days. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) +
+ ## v0.0.3 (2022-01-08) ### Documentation @@ -28,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 32 commits contributed to the release over the course of 151 calendar days. + - 33 commits contributed to the release over the course of 151 calendar days. - 21 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -39,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49)) - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee)) diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 7e257f854..86b157737 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### New Features + + - enable use_router + - connect an onchange listener + - flatten props attrs + +### Bug Fixes + + - attach router listener to subscriber list + - use_route should subscribe to changes to the route + +### Commit Statistics + + + + - 18 commits contributed to the release over the course of 14 calendar days. + - 7 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a)) + - enable use_router ([`e24957f`](https://github.comgit//DioxusLabs/dioxus/commit/e24957fc191476184816fe5dee249f691170d4ae)) + - add docs to router UseRouteListener ([`79e0993`](https://github.comgit//DioxusLabs/dioxus/commit/79e09934aa685d03d6e0b323723bc1cd537d74d9)) + - Merge pull request #166 from DioxusLabs/jk/default-assets-desktop ([`ccbb955`](https://github.comgit//DioxusLabs/dioxus/commit/ccbb955b7b24bd4e1c5aa40e813a2872ae474a69)) + - rustfmt ([`9da46eb`](https://github.comgit//DioxusLabs/dioxus/commit/9da46eb7bc207997ca7779c58fcb2a9645dfa9d0)) + - Make log message in Link component trace level, not debug ([`72c6bb3`](https://github.comgit//DioxusLabs/dioxus/commit/72c6bb3d0b7253f084f7e3bcf55d458cb4adeedb)) + - attach router listener to subscriber list ([`5ee9d6c`](https://github.comgit//DioxusLabs/dioxus/commit/5ee9d6c4348a2f51adac827f715fb138918d1dc6)) + - connect an onchange listener ([`29ed7eb`](https://github.comgit//DioxusLabs/dioxus/commit/29ed7ebece26e9d53925af55f2f34a8fd8241405)) + - use_route should subscribe to changes to the route ([`a21e7d4`](https://github.comgit//DioxusLabs/dioxus/commit/a21e7d4dd168129da06f535f9dc4b1de724617cb)) + - Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471)) + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) + - flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d)) + - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f)) + - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11)) + - Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97)) + - Add a warning when Link it called outside of a Router context ([`6408058`](https://github.comgit//DioxusLabs/dioxus/commit/64080588d02c54a1d380116cbecdd17de16d2392)) + - Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f)) + - Fix handling of re-renders in the Router ([`81c094e`](https://github.comgit//DioxusLabs/dioxus/commit/81c094ed29fcdc5c6099492dd6ab09a59b79252c)) +
+ ## v0.1.0 (2022-01-08) ### Documentation @@ -33,7 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 42 commits contributed to the release over the course of 352 calendar days. + - 50 commits contributed to the release over the course of 352 calendar days. - 35 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -44,8 +92,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49)) + - More WIP router implementation ([`e06eac1`](https://github.comgit//DioxusLabs/dioxus/commit/e06eac1ce5c6bb0a6680482574d82b16a141a626)) + - Implement UseRoute segment method ([`c9408da`](https://github.comgit//DioxusLabs/dioxus/commit/c9408da7310423b1676fab6d41635f9a8000d89e)) - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) + - Implement router matching for path parameters ([`f8a7e1c`](https://github.comgit//DioxusLabs/dioxus/commit/f8a7e1cd8255fd7d0116384247e0e305bb73bf3d)) + - Commit WIP on router ([`3c6142f`](https://github.comgit//DioxusLabs/dioxus/commit/3c6142fb9d8b5f715adf0fb30c2f3534b9ecd923)) + - Add more trace messages to the RouterService code ([`3a5b417`](https://github.comgit//DioxusLabs/dioxus/commit/3a5b417ad1639a31e9aac9f41b05d8b3f074b128)) - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) + - Fix typo in RouterService struct's "registered_routes" field name ([`d367e0f`](https://github.comgit//DioxusLabs/dioxus/commit/d367e0f89f1b31484bde42efaf10c3531aed1d64)) + - Add title prop to Link ([`e22ba5b`](https://github.comgit//DioxusLabs/dioxus/commit/e22ba5b1e52fe00e08e0b6ac0abae29b6068b8f0)) - add prevent default attribute and upgrade router ([`427b126`](https://github.comgit//DioxusLabs/dioxus/commit/427b126bc17336d5d14d56eb7fddb8e07752495f)) - memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c)) - bump all versions ([`4f92ba4`](https://github.comgit//DioxusLabs/dioxus/commit/4f92ba41602d706449c1bddabd49829873ee72eb)) diff --git a/packages/ssr/CHANGELOG.md b/packages/ssr/CHANGELOG.md index 73cee67b0..43e1960c1 100644 --- a/packages/ssr/CHANGELOG.md +++ b/packages/ssr/CHANGELOG.md @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Bug Fixes + + - ssr respects bool attrs + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 21 calendar days. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3)) + - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d)) + - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f)) + - ssr respects bool attrs ([`255f58a`](https://github.comgit//DioxusLabs/dioxus/commit/255f58af63e002339b02bf91c5b85cc5ec917428)) + - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11)) +
+ ## v0.1.2 (2022-01-08) ### Documentation @@ -47,7 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 84 commits contributed to the release over the course of 357 calendar days. + - 85 commits contributed to the release over the course of 357 calendar days. - 75 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -58,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49)) - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee)) diff --git a/packages/web/CHANGELOG.md b/packages/web/CHANGELOG.md index b81747e95..3c27f3f94 100644 --- a/packages/web/CHANGELOG.md +++ b/packages/web/CHANGELOG.md @@ -5,6 +5,60 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Documentation + + - fix web doc example and use &mut for builders everywhere + +### New Features + + - add panic hook by default + - remove dioxus id on non-event elements + - wire up both desktop and web + - bool attr white list + +### Bug Fixes + + - webconfig should take &mut self + - format code + - ssr + hydration event listeners + - add exclusion list + +### Commit Statistics + + + + - 17 commits contributed to the release over the course of 21 calendar days. + - 10 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - webconfig should take &mut self ([`ae676d9`](https://github.comgit//DioxusLabs/dioxus/commit/ae676d9d816fcbf88d110157124443a5b3aef9eb)) + - fix web doc example and use &mut for builders everywhere ([`a239d2b`](https://github.comgit//DioxusLabs/dioxus/commit/a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63)) + - add panic hook by default ([`1406c90`](https://github.comgit//DioxusLabs/dioxus/commit/1406c9020bb3102635737dc2d13e5b3171499a18)) + - Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3)) + - Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401)) + - remove dioxus id on non-event elements ([`95e93ed`](https://github.comgit//DioxusLabs/dioxus/commit/95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36)) + - feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9)) + - feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3)) + - Merge pull request #101 from alexkirsz/ci ([`29bf424`](https://github.comgit//DioxusLabs/dioxus/commit/29bf424b0976b95ff645bb128d0e758cf0186614)) + - Merge pull request #139 from DioxusLabs/jk/provide-context-any ([`70f2ef4`](https://github.comgit//DioxusLabs/dioxus/commit/70f2ef43db5b6737bd9bcbfc1aa21c834ce4b395)) + - Merge branch 'master' into jk/unify ([`824defa`](https://github.comgit//DioxusLabs/dioxus/commit/824defa2dbcc16d66588b3976699d89b65a8a068)) + - wire up both desktop and web ([`05331dd`](https://github.comgit//DioxusLabs/dioxus/commit/05331ddd8033f6997d4916179b62f4d62f832988)) + - format code ([`9256161`](https://github.comgit//DioxusLabs/dioxus/commit/92561612c727e73356d7d36e16af39aacf02a56d)) + - ssr + hydration event listeners ([`c1a5d4e`](https://github.comgit//DioxusLabs/dioxus/commit/c1a5d4e11f5277e6ee644f670dc6fdd68f2202f1)) + - web now links against the js interprter code ([`10db6ad`](https://github.comgit//DioxusLabs/dioxus/commit/10db6ad65bd23dabf60d0afbe6ff82cda8220a8b)) + - bool attr white list ([`8f4aa84`](https://github.comgit//DioxusLabs/dioxus/commit/8f4aa84f1a4f2443b34d81ee42490564e168de53)) + - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b)) +
+ ## v0.0.4 (2022-01-08) ### Documentation @@ -84,7 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 203 commits contributed to the release over the course of 358 calendar days. - - 190 commits where understood as [conventional](https://www.conventionalcommits.org). + - 189 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages ### Commit Details @@ -94,10 +148,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49)) + - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a)) - Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111)) - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307)) - - add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b)) - - handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a)) - Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee)) - Merge pull request #80 from DioxusLabs/jk/router2dotoh ([`cdc2d8e`](https://github.comgit//DioxusLabs/dioxus/commit/cdc2d8ec6d123245c2ea5f6d10af02b6a6833994)) - memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c)) From 06723ad63fd21c5e50d666ff0809cd9ca07a7911 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:23:41 -0500 Subject: [PATCH 018/103] fix: add interpreter to desktop package so it publishes --- packages/desktop/src/interpreter.js | 548 ++++++++++++++++++++++++++++ packages/desktop/src/lib.rs | 2 +- 2 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 packages/desktop/src/interpreter.js diff --git a/packages/desktop/src/interpreter.js b/packages/desktop/src/interpreter.js new file mode 100644 index 000000000..61ba649c7 --- /dev/null +++ b/packages/desktop/src/interpreter.js @@ -0,0 +1,548 @@ +export function main() { + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.rpc.call("initialize"); + } +} +export class Interpreter { + root; + stack; + listeners; + handlers; + lastNodeWasText; + nodes; + constructor(root) { + this.root = root; + this.stack = [root]; + this.listeners = {}; + this.handlers = {}; + this.lastNodeWasText = false; + this.nodes = [root]; + } + top() { + return this.stack[this.stack.length - 1]; + } + pop() { + return this.stack.pop(); + } + PushRoot(root) { + const node = this.nodes[root]; + this.stack.push(node); + } + AppendChildren(many) { + let root = this.stack[this.stack.length - (1 + many)]; + let to_add = this.stack.splice(this.stack.length - many); + for (let i = 0; i < many; i++) { + root.appendChild(to_add[i]); + } + } + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; + let els = this.stack.splice(this.stack.length - m); + root.replaceWith(...els); + } + InsertAfter(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.after(...new_nodes); + } + InsertBefore(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.before(...new_nodes); + } + Remove(root) { + let node = this.nodes[root]; + if (node !== undefined) { + node.remove(); + } + } + CreateTextNode(text, root) { + // todo: make it so the types are okay + const node = document.createTextNode(text); + this.nodes[root] = node; + this.stack.push(node); + } + CreateElement(tag, root) { + const el = document.createElement(tag); + // el.setAttribute("data-dioxus-id", `${root}`); + this.nodes[root] = el; + this.stack.push(el); + } + CreateElementNs(tag, root, ns) { + let el = document.createElementNS(ns, tag); + this.stack.push(el); + this.nodes[root] = el; + } + CreatePlaceholder(root) { + let el = document.createElement("pre"); + el.hidden = true; + this.stack.push(el); + this.nodes[root] = el; + } + NewEventListener(event_name, root, handler) { + const element = this.nodes[root]; + element.setAttribute("data-dioxus-id", `${root}`); + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } else { + this.listeners[event_name]++; + } + } + RemoveEventListener(root, event_name) { + const element = this.nodes[root]; + element.removeAttribute(`data-dioxus-id`); + this.listeners[event_name]--; + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; + } + } + SetText(root, text) { + this.nodes[root].textContent = text; + } + SetAttribute(root, field, value, ns) { + const name = field; + const node = this.nodes[root]; + if (ns == "style") { + // @ts-ignore + node.style[name] = value; + } else if (ns != null || ns != undefined) { + node.setAttributeNS(ns, name, value); + } else { + switch (name) { + case "value": + if (value != node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value == "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } else { + node.setAttribute(name, value); + } + } + } + } + RemoveAttribute(root, name) { + const node = this.nodes[root]; + node.removeAttribute(name); + if (name === "value") { + node.value = ""; + } + if (name === "checked") { + node.checked = false; + } + if (name === "selected") { + node.selected = false; + } + } + handleEdits(edits) { + this.stack.push(this.root); + for (let edit of edits) { + this.handleEdit(edit); + } + } + handleEdit(edit) { + switch (edit.type) { + case "PushRoot": + this.PushRoot(edit.root); + break; + case "AppendChildren": + this.AppendChildren(edit.many); + break; + case "ReplaceWith": + this.ReplaceWith(edit.root, edit.m); + break; + case "InsertAfter": + this.InsertAfter(edit.root, edit.n); + break; + case "InsertBefore": + this.InsertBefore(edit.root, edit.n); + break; + case "Remove": + this.Remove(edit.root); + break; + case "CreateTextNode": + this.CreateTextNode(edit.text, edit.root); + break; + case "CreateElement": + this.CreateElement(edit.tag, edit.root); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.root, edit.ns); + break; + case "CreatePlaceholder": + this.CreatePlaceholder(edit.root); + break; + case "RemoveEventListener": + this.RemoveEventListener(edit.root, edit.event_name); + break; + case "NewEventListener": + // this handler is only provided on desktop implementations since this + // method is not used by the web implementation + let handler = (event) => { + let target = event.target; + if (target != null) { + let realId = target.getAttribute(`data-dioxus-id`); + // walk the tree to find the real element + while (realId == null && target.parentElement != null) { + target = target.parentElement; + realId = target.getAttribute(`data-dioxus-id`); + } + const shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + let contents = serialize_event(event); + if (shouldPreventDefault === `on${event.type}`) { + event.preventDefault(); + } + if (event.type == "submit") { + event.preventDefault(); + } + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if ( + href !== "" && + href !== null && + href !== undefined && + realId != null + ) { + window.rpc.call("browser_open", { + mounted_dom_id: parseInt(realId), + href, + }); + } + } + } + } + if (realId == null) { + return; + } + window.rpc.call("user_event", { + event: edit.event_name, + mounted_dom_id: parseInt(realId), + contents: contents, + }); + } + }; + this.NewEventListener(edit.event_name, edit.root, handler); + break; + case "SetText": + this.SetText(edit.root, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.root, edit.name); + break; + } + } +} +function serialize_event(event) { + switch (event.type) { + case "copy": + case "cut": + case "past": { + return {}; + } + case "compositionend": + case "compositionstart": + case "compositionupdate": { + let { data } = event; + return { + data, + }; + } + case "keydown": + case "keypress": + case "keyup": { + let { + charCode, + key, + altKey, + ctrlKey, + metaKey, + keyCode, + shiftKey, + location, + repeat, + which, + } = event; + return { + char_code: charCode, + key: key, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + key_code: keyCode, + shift_key: shiftKey, + location: location, + repeat: repeat, + which: which, + locale: "locale", + }; + } + case "focus": + case "blur": { + return {}; + } + case "change": { + let target = event.target; + let value; + if (target.type === "checkbox" || target.type === "radio") { + value = target.checked ? "true" : "false"; + } else { + value = target.value ?? target.textContent; + } + return { + value: value, + }; + } + case "input": + case "invalid": + case "reset": + case "submit": { + let target = event.target; + let value = target.value ?? target.textContent; + if (target.type == "checkbox") { + value = target.checked ? "true" : "false"; + } + return { + value: value, + }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "drag": + case "dragend": + case "dragenter": + case "dragexit": + case "dragleave": + case "dragover": + case "dragstart": + case "drop": + case "mousedown": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "mouseup": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; + } + case "pointerdown": + case "pointermove": + case "pointerup": + case "pointercancel": + case "gotpointercapture": + case "lostpointercapture": + case "pointerenter": + case "pointerleave": + case "pointerover": + case "pointerout": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + pointerId, + width, + height, + pressure, + tangentialPressure, + tiltX, + tiltY, + twist, + pointerType, + isPrimary, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + pointer_id: pointerId, + width: width, + height: height, + pressure: pressure, + tangential_pressure: tangentialPressure, + tilt_x: tiltX, + tilt_y: tiltY, + twist: twist, + pointer_type: pointerType, + is_primary: isPrimary, + }; + } + case "select": { + return {}; + } + case "touchcancel": + case "touchend": + case "touchmove": + case "touchstart": { + const { altKey, ctrlKey, metaKey, shiftKey } = event; + return { + // changed_touches: event.changedTouches, + // target_touches: event.targetTouches, + // touches: event.touches, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, + }; + } + case "scroll": { + return {}; + } + case "wheel": { + const { deltaX, deltaY, deltaZ, deltaMode } = event; + return { + delta_x: deltaX, + delta_y: deltaY, + delta_z: deltaZ, + delta_mode: deltaMode, + }; + } + case "animationstart": + case "animationend": + case "animationiteration": { + const { animationName, elapsedTime, pseudoElement } = event; + return { + animation_name: animationName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "transitionend": { + const { propertyName, elapsedTime, pseudoElement } = event; + return { + property_name: propertyName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "abort": + case "canplay": + case "canplaythrough": + case "durationchange": + case "emptied": + case "encrypted": + case "ended": + case "error": + case "loadeddata": + case "loadedmetadata": + case "loadstart": + case "pause": + case "play": + case "playing": + case "progress": + case "ratechange": + case "seeked": + case "seeking": + case "stalled": + case "suspend": + case "timeupdate": + case "volumechange": + case "waiting": { + return {}; + } + case "toggle": { + return {}; + } + default: { + return {}; + } + } +} +const bool_attrs = { + allowfullscreen: true, + allowpaymentrequest: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, + truespeed: true, +}; diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 36b017077..f3e370230 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -220,7 +220,7 @@ pub fn launch_with_props( } else if trimmed == "index.js" { wry::http::ResponseBuilder::new() .mimetype("text/javascript") - .body(include_bytes!("../../jsinterpreter/interpreter.js").to_vec()) + .body(include_bytes!("./interpreter.js").to_vec()) } else { // Read the file content from file path use std::fs::read; From 8056138f860d3f5a88f7ead580e1efb026263da1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:27:40 -0500 Subject: [PATCH 019/103] fix: explicit default for event handler rust derives default of T: Default, even when that's not a necessary requirement This fixes that --- packages/core/src/nodes.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e30a07001..0efd64177 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -351,11 +351,18 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; /// } /// /// ``` -#[derive(Default)] pub struct EventHandler<'bump, T = ()> { pub callback: RefCell>>, } +impl<'a> Default for EventHandler<'a> { + fn default() -> Self { + Self { + callback: RefCell::new(None), + } + } +} + impl EventHandler<'_, T> { /// Call this event handler with the appropriate event type pub fn call(&self, event: T) { From 1b2a0053ef8aedf71cc4adad8c99f4db00bd8743 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:35:54 -0500 Subject: [PATCH 020/103] Release dioxus-core-macro v0.1.7 --- Cargo.toml | 2 +- packages/core-macro/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 884513a5b..105ce5050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "./packages/core", version = "^0.1.7" } dioxus-html = { path = "./packages/html", version = "^0.1.4", optional = true } -dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.6", optional = true } +dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true } dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true } diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index ee3dc2883..eb8210923 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core-macro" -version = "0.1.6" +version = "0.1.7" authors = ["Jonathan Kelley"] edition = "2021" description = "Core macro for Dioxus Virtual DOM" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 123013e5e..cb880d51b 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.12.0", features = [ "rt", "time", ], optional = true, default-features = false } -dioxus-core-macro = { path = "../core-macro", version = "^0.1.6" } +dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.4" } webbrowser = "0.5.5" mime_guess = "2.0.3" diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 032dbe3bf..4dfb03a6b 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version ="^0.1.7", default-features = false } dioxus-html = { path = "../html", version ="^0.1.4", default-features = false } -dioxus-core-macro = { path = "../core-macro", version ="^0.1.6"} +dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } serde = "1" url = "2.2.2" From f965030b80491f47ecdbff4698ec4d891a5d4cbe Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:36:10 -0500 Subject: [PATCH 021/103] Release dioxus-core v0.1.8 --- Cargo.toml | 2 +- packages/core/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/hooks/Cargo.toml | 2 +- packages/html/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/ssr/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 105ce5050..4839ed024 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "./packages/core", version = "^0.1.7" } +dioxus-core = { path = "./packages/core", version = "^0.1.8" } dioxus-html = { path = "./packages/html", version = "^0.1.4", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index f54c6d5d9..c6cf62115 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core" -version = "0.1.7" +version = "0.1.8" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index cb880d51b..a382b7166 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.1.7", features = ["serialize"] } +dioxus-core = { path = "../core", version = "^0.1.8", features = ["serialize"] } argh = "0.1.4" serde = "1.0.120" serde_json = "1.0.61" diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 375fef087..05368f171 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -12,4 +12,4 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version ="^0.1.7"} +dioxus-core = { path = "../../packages/core", version = "^0.1.8" } diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 3c0f07b9b..b46f73c75 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/dioxus" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version ="^0.1.7"} +dioxus-core = { path = "../core", version = "^0.1.8" } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 4dfb03a6b..3a6912f18 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version ="^0.1.7", default-features = false } +dioxus-core = { path = "../core", version = "^0.1.8", default-features = false } dioxus-html = { path = "../html", version ="^0.1.4", default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 754fbbb78..77cf055a5 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version ="^0.1.7", features = ["serialize"] } +dioxus-core = { path = "../core", version = "^0.1.8", features = ["serialize"] } [dev-dependencies] diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index a5befe7b3..502164e39 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version = "^0.1.7" } +dioxus-core = { path = "../core", version = "^0.1.8" } dioxus-html = { path = "../html", version = "^0.1.4" } js-sys = "0.3" wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } From 11ba189eab817507f71a3dfd66a403d0fae33b98 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:36:24 -0500 Subject: [PATCH 022/103] Release dioxus-html v0.1.5 --- Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/html/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4839ed024..d31c9f555 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "./packages/core", version = "^0.1.8" } -dioxus-html = { path = "./packages/html", version = "^0.1.4", optional = true } +dioxus-html = { path = "./packages/html", version = "^0.1.5", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index a382b7166..0a8a1e5d9 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.12.0", features = [ "time", ], optional = true, default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } -dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.4" } +dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.5" } webbrowser = "0.5.5" mime_guess = "2.0.3" diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index b46f73c75..73e6b4a8a 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-html" -version = "0.1.4" +version = "0.1.5" authors = ["Jonathan Kelley"] edition = "2018" description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 3a6912f18..c60a6b105 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.8", default-features = false } -dioxus-html = { path = "../html", version ="^0.1.4", default-features = false } +dioxus-html = { path = "../html", version = "^0.1.5", default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } serde = "1" diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 502164e39..48b871282 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.8" } -dioxus-html = { path = "../html", version = "^0.1.4" } +dioxus-html = { path = "../html", version = "^0.1.5" } js-sys = "0.3" wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } lazy_static = "1.4.0" From 539ca2d655c0e4eba7d4bfcb359ec2e12d915800 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:37:03 -0500 Subject: [PATCH 023/103] Release dioxus-hooks v0.1.7 --- Cargo.toml | 2 +- packages/hooks/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d31c9f555..3c954df9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "./packages/core", version = "^0.1.8" } dioxus-html = { path = "./packages/html", version = "^0.1.5", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } -dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true } +dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true } dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true } dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true } diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 05368f171..aad2952bf 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-hooks" -version = "0.1.6" +version = "0.1.7" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for a remote webview instance" From 3c2d5985da9c0bb61eb7bd0eec519baa23df4c59 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:37:34 -0500 Subject: [PATCH 024/103] Release dioxus-ssr v0.1.3 --- Cargo.toml | 2 +- packages/ssr/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c954df9c..5977f0508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true } dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true } -dioxus-ssr = { path = "./packages/ssr", version = "^0.1.2", optional = true } +dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true } dioxus-router = { path = "./packages/router", version = "^0.1.0", optional = true } dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 77cf055a5..039c2bae4 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-ssr" -version = "0.1.2" +version = "0.1.3" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus render-to-string" From 2c51a090e3d9d30c61c6d19a317f50d9df5cd27b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:37:50 -0500 Subject: [PATCH 025/103] Release dioxus-web v0.0.5 --- Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5977f0508..62d25783a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ dioxus-html = { path = "./packages/html", version = "^0.1.5", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true } -dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true } +dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true } dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true } dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 48b871282..926b52429 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-web" -version = "0.0.4" +version = "0.0.5" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" From 189ddd8c51a5cacef440580962c9166236ba3471 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:39:21 -0500 Subject: [PATCH 026/103] fix: move bindings into package --- packages/web/src/bindings.rs | 2 +- packages/web/src/interpreter.js | 499 ++++++++++++++++++++++++++++++++ 2 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/interpreter.js diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs index 9b0c2ebfb..fa96e3230 100644 --- a/packages/web/src/bindings.rs +++ b/packages/web/src/bindings.rs @@ -2,7 +2,7 @@ use js_sys::Function; use wasm_bindgen::prelude::*; use web_sys::{Element, Node}; -#[wasm_bindgen(module = "/../jsinterpreter/interpreter.js")] +#[wasm_bindgen(module = "/src/interpreter.js")] extern "C" { pub type Interpreter; diff --git a/packages/web/src/interpreter.js b/packages/web/src/interpreter.js new file mode 100644 index 000000000..ea50ec3d3 --- /dev/null +++ b/packages/web/src/interpreter.js @@ -0,0 +1,499 @@ +export function main() { + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.rpc.call("initialize"); + } +} +export class Interpreter { + root; + stack; + listeners; + handlers; + lastNodeWasText; + nodes; + constructor(root) { + this.root = root; + this.stack = [root]; + this.listeners = {}; + this.handlers = {}; + this.lastNodeWasText = false; + this.nodes = [root]; + } + top() { + return this.stack[this.stack.length - 1]; + } + pop() { + return this.stack.pop(); + } + PushRoot(root) { + const node = this.nodes[root]; + this.stack.push(node); + } + AppendChildren(many) { + let root = this.stack[this.stack.length - (1 + many)]; + let to_add = this.stack.splice(this.stack.length - many); + for (let i = 0; i < many; i++) { + root.appendChild(to_add[i]); + } + } + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; + let els = this.stack.splice(this.stack.length - m); + root.replaceWith(...els); + } + InsertAfter(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.after(...new_nodes); + } + InsertBefore(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.before(...new_nodes); + } + Remove(root) { + let node = this.nodes[root]; + if (node !== undefined) { + node.remove(); + } + } + CreateTextNode(text, root) { + // todo: make it so the types are okay + const node = document.createTextNode(text); + this.nodes[root] = node; + this.stack.push(node); + } + CreateElement(tag, root) { + const el = document.createElement(tag); + // el.setAttribute("data-dioxus-id", `${root}`); + this.nodes[root] = el; + this.stack.push(el); + } + CreateElementNs(tag, root, ns) { + let el = document.createElementNS(ns, tag); + this.stack.push(el); + this.nodes[root] = el; + } + CreatePlaceholder(root) { + let el = document.createElement("pre"); + el.hidden = true; + this.stack.push(el); + this.nodes[root] = el; + } + NewEventListener(event_name, root, handler) { + const element = this.nodes[root]; + element.setAttribute("data-dioxus-id", `${root}`); + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } + else { + this.listeners[event_name]++; + } + } + RemoveEventListener(root, event_name) { + const element = this.nodes[root]; + element.removeAttribute(`data-dioxus-id`); + this.listeners[event_name]--; + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; + } + } + SetText(root, text) { + this.nodes[root].textContent = text; + } + SetAttribute(root, field, value, ns) { + const name = field; + const node = this.nodes[root]; + if (ns == "style") { + // @ts-ignore + node.style[name] = value; + } + else if (ns != null || ns != undefined) { + node.setAttributeNS(ns, name, value); + } + else { + switch (name) { + case "value": + if (value != node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value == "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } + else { + node.setAttribute(name, value); + } + } + } + } + RemoveAttribute(root, name) { + const node = this.nodes[root]; + node.removeAttribute(name); + if (name === "value") { + node.value = ""; + } + if (name === "checked") { + node.checked = false; + } + if (name === "selected") { + node.selected = false; + } + } + handleEdits(edits) { + this.stack.push(this.root); + for (let edit of edits) { + this.handleEdit(edit); + } + } + handleEdit(edit) { + switch (edit.type) { + case "PushRoot": + this.PushRoot(edit.root); + break; + case "AppendChildren": + this.AppendChildren(edit.many); + break; + case "ReplaceWith": + this.ReplaceWith(edit.root, edit.m); + break; + case "InsertAfter": + this.InsertAfter(edit.root, edit.n); + break; + case "InsertBefore": + this.InsertBefore(edit.root, edit.n); + break; + case "Remove": + this.Remove(edit.root); + break; + case "CreateTextNode": + this.CreateTextNode(edit.text, edit.root); + break; + case "CreateElement": + this.CreateElement(edit.tag, edit.root); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.root, edit.ns); + break; + case "CreatePlaceholder": + this.CreatePlaceholder(edit.root); + break; + case "RemoveEventListener": + this.RemoveEventListener(edit.root, edit.event_name); + break; + case "NewEventListener": + // this handler is only provided on desktop implementations since this + // method is not used by the web implementation + let handler = (event) => { + let target = event.target; + if (target != null) { + let realId = target.getAttribute(`data-dioxus-id`); + // walk the tree to find the real element + while (realId == null && target.parentElement != null) { + target = target.parentElement; + realId = target.getAttribute(`data-dioxus-id`); + } + const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + let contents = serialize_event(event); + if (shouldPreventDefault === `on${event.type}`) { + event.preventDefault(); + } + if (event.type == "submit") { + event.preventDefault(); + } + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined && realId != null) { + window.rpc.call("browser_open", { + mounted_dom_id: parseInt(realId), + href + }); + } + } + } + } + if (realId == null) { + return; + } + window.rpc.call("user_event", { + event: edit.event_name, + mounted_dom_id: parseInt(realId), + contents: contents, + }); + } + }; + this.NewEventListener(edit.event_name, edit.root, handler); + break; + case "SetText": + this.SetText(edit.root, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.root, edit.name); + break; + } + } +} +function serialize_event(event) { + switch (event.type) { + case "copy": + case "cut": + case "past": { + return {}; + } + case "compositionend": + case "compositionstart": + case "compositionupdate": { + let { data } = event; + return { + data, + }; + } + case "keydown": + case "keypress": + case "keyup": { + let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; + return { + char_code: charCode, + key: key, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + key_code: keyCode, + shift_key: shiftKey, + location: location, + repeat: repeat, + which: which, + locale: "locale", + }; + } + case "focus": + case "blur": { + return {}; + } + case "change": { + let target = event.target; + let value; + if (target.type === "checkbox" || target.type === "radio") { + value = target.checked ? "true" : "false"; + } + else { + value = target.value ?? target.textContent; + } + return { + value: value, + }; + } + case "input": + case "invalid": + case "reset": + case "submit": { + let target = event.target; + let value = target.value ?? target.textContent; + if (target.type == "checkbox") { + value = target.checked ? "true" : "false"; + } + return { + value: value, + }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "drag": + case "dragend": + case "dragenter": + case "dragexit": + case "dragleave": + case "dragover": + case "dragstart": + case "drop": + case "mousedown": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "mouseup": { + const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; + } + case "pointerdown": + case "pointermove": + case "pointerup": + case "pointercancel": + case "gotpointercapture": + case "lostpointercapture": + case "pointerenter": + case "pointerleave": + case "pointerover": + case "pointerout": { + const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + pointer_id: pointerId, + width: width, + height: height, + pressure: pressure, + tangential_pressure: tangentialPressure, + tilt_x: tiltX, + tilt_y: tiltY, + twist: twist, + pointer_type: pointerType, + is_primary: isPrimary, + }; + } + case "select": { + return {}; + } + case "touchcancel": + case "touchend": + case "touchmove": + case "touchstart": { + const { altKey, ctrlKey, metaKey, shiftKey, } = event; + return { + // changed_touches: event.changedTouches, + // target_touches: event.targetTouches, + // touches: event.touches, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, + }; + } + case "scroll": { + return {}; + } + case "wheel": { + const { deltaX, deltaY, deltaZ, deltaMode, } = event; + return { + delta_x: deltaX, + delta_y: deltaY, + delta_z: deltaZ, + delta_mode: deltaMode, + }; + } + case "animationstart": + case "animationend": + case "animationiteration": { + const { animationName, elapsedTime, pseudoElement, } = event; + return { + animation_name: animationName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "transitionend": { + const { propertyName, elapsedTime, pseudoElement, } = event; + return { + property_name: propertyName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "abort": + case "canplay": + case "canplaythrough": + case "durationchange": + case "emptied": + case "encrypted": + case "ended": + case "error": + case "loadeddata": + case "loadedmetadata": + case "loadstart": + case "pause": + case "play": + case "playing": + case "progress": + case "ratechange": + case "seeked": + case "seeking": + case "stalled": + case "suspend": + case "timeupdate": + case "volumechange": + case "waiting": { + return {}; + } + case "toggle": { + return {}; + } + default: { + return {}; + } + } +} +const bool_attrs = { + allowfullscreen: true, + allowpaymentrequest: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, + truespeed: true, +}; From c72a90f42ee93d2e5c44a761e6702a22696a6ba9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:39:24 -0500 Subject: [PATCH 027/103] Release dioxus-web v0.0.5 From d570bb6c42c59ef76bee7f55be123bdbee688794 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:40:03 -0500 Subject: [PATCH 028/103] Release dioxus-router v0.1.1 --- Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 62d25783a..e1184c2de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true } dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true } dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true } -dioxus-router = { path = "./packages/router", version = "^0.1.0", optional = true } +dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true } dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true } # dioxus-liveview = { path = "./packages/liveview", optional = true } diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index c60a6b105..a2cd4a559 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-router" -version = "0.1.0" +version = "0.1.1" edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" license = "MIT/Apache-2.0" From 578c1dc069be48818a2f36e3b86927fd001b336e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:42:06 -0500 Subject: [PATCH 029/103] fix: enable default for any eventhandler --- packages/core/src/nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0efd64177..b18aeaf2d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -355,7 +355,7 @@ pub struct EventHandler<'bump, T = ()> { pub callback: RefCell>>, } -impl<'a> Default for EventHandler<'a> { +impl<'a, T> Default for EventHandler<'a, T> { fn default() -> Self { Self { callback: RefCell::new(None), From f6c6134d676edc05278d21817ddf06f7bbe1fbf8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:42:38 -0500 Subject: [PATCH 030/103] Release dioxus-core v0.1.9 --- Cargo.toml | 2 +- packages/core/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/hooks/Cargo.toml | 2 +- packages/html/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/ssr/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e1184c2de..76630ca58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "./packages/core", version = "^0.1.8" } +dioxus-core = { path = "./packages/core", version = "^0.1.9" } dioxus-html = { path = "./packages/html", version = "^0.1.5", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index c6cf62115..03b378e88 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core" -version = "0.1.8" +version = "0.1.9" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 0a8a1e5d9..e7700348c 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.1.8", features = ["serialize"] } +dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] } argh = "0.1.4" serde = "1.0.120" serde_json = "1.0.61" diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index aad2952bf..7d8e4d26a 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -12,4 +12,4 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version = "^0.1.8" } +dioxus-core = { path = "../../packages/core", version = "^0.1.9" } diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 73e6b4a8a..339f47f3a 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/dioxus" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version = "^0.1.8" } +dioxus-core = { path = "../core", version = "^0.1.9" } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index a2cd4a559..77345dd3c 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.1.8", default-features = false } +dioxus-core = { path = "../core", version = "^0.1.9", default-features = false } dioxus-html = { path = "../html", version = "^0.1.5", default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 039c2bae4..02f51f453 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.1.8", features = ["serialize"] } +dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] } [dev-dependencies] diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 926b52429..c2232775c 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version = "^0.1.8" } +dioxus-core = { path = "../core", version = "^0.1.9" } dioxus-html = { path = "../html", version = "^0.1.5" } js-sys = "0.3" wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } From 6339fa8188bac0cdc62318286776c54d57744805 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:42:52 -0500 Subject: [PATCH 031/103] Release dioxus-html v0.1.6, dioxus-router v0.1.1 --- Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/html/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/web/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76630ca58..0cbecbc17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "./packages/core", version = "^0.1.9" } -dioxus-html = { path = "./packages/html", version = "^0.1.5", optional = true } +dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index e7700348c..18bd2b082 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.12.0", features = [ "time", ], optional = true, default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } -dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.5" } +dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" } webbrowser = "0.5.5" mime_guess = "2.0.3" diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 339f47f3a..293d4c465 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-html" -version = "0.1.5" +version = "0.1.6" authors = ["Jonathan Kelley"] edition = "2018" description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 77345dd3c..1c6ed24d2 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9", default-features = false } -dioxus-html = { path = "../html", version = "^0.1.5", default-features = false } +dioxus-html = { path = "../html", version = "^0.1.6", default-features = false } dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } serde = "1" diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index c2232775c..a14d6cb0a 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9" } -dioxus-html = { path = "../html", version = "^0.1.5" } +dioxus-html = { path = "../html", version = "^0.1.6" } js-sys = "0.3" wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } lazy_static = "1.4.0" From 9b3192323e4cd43cbe4b4432ac413a1904a43a14 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:43:43 -0500 Subject: [PATCH 032/103] Release dioxus-desktop v0.1.6 --- Cargo.toml | 2 +- packages/desktop/Cargo.toml | 2 +- packages/mobile/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0cbecbc17..6c4272863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", option dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true } dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true } -dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true } +dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true } dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true } dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 18bd2b082..0d393e1ef 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-desktop" -version = "0.1.5" +version = "0.1.6" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for a remote webview instance" diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 373814260..9da440343 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -12,4 +12,4 @@ license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-desktop = { path = "../desktop", version ="^0.1.5"} +dioxus-desktop = { path = "../desktop", version = "^0.1.6" } From 875977f5a666804b100f089200ca3db1e87ac93f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Jan 2022 10:48:41 -0500 Subject: [PATCH 033/103] publish: update 0.1.8 for dioxus --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c4272863..e0fbc94b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus" -version = "0.1.7" +version = "0.1.8" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" From 3d3d18554da562faf68b8231787ecc59fde79d62 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sat, 29 Jan 2022 10:36:44 -0600 Subject: [PATCH 034/103] Fix misspelled variable name in usestate.rs --- packages/hooks/src/usestate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 914587c13..bce5b2b88 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -153,8 +153,8 @@ impl UseState { /// } /// ``` pub fn modify(&self, f: impl FnOnce(&T) -> T) { - let curernt = self.slot.borrow(); - let new_val = f(curernt.as_ref()); + let current = self.slot.borrow(); + let new_val = f(current.as_ref()); (self.setter)(new_val); } From 00ff54bb3bbb1c6cf98d4050f7a1b66c30b0c673 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sat, 29 Jan 2022 10:43:10 -0600 Subject: [PATCH 035/103] Fix typo in comment in use_shared_state.rs --- packages/hooks/src/use_shared_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 9b59601b5..a05e99d7d 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -33,7 +33,7 @@ impl ProvidedStateInner { /// This hook provides some relatively light ergonomics around shared state. /// /// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type -/// ergonimics in a pinch, with zero cost. +/// ergonomics in a pinch, with zero cost. /// /// # Example /// From 169028705dc5b64f33a880d3b7bb845d44cf882f Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 31 Jan 2022 00:33:14 +0800 Subject: [PATCH 036/103] feat: add `DesktopContext` --- examples/borderless.rs | 21 +++++ packages/desktop/src/desktop_context.rs | 110 ++++-------------------- packages/desktop/src/lib.rs | 31 +++++++ 3 files changed, 71 insertions(+), 91 deletions(-) create mode 100644 examples/borderless.rs diff --git a/examples/borderless.rs b/examples/borderless.rs new file mode 100644 index 000000000..18bbbb80b --- /dev/null +++ b/examples/borderless.rs @@ -0,0 +1,21 @@ +use dioxus::prelude::*; +use dioxus_desktop::desktop_context::DesktopContext; + +fn main() { + dioxus::desktop::launch_cfg(app, |cfg| { + cfg.with_window(|w| { + w.with_title("BorderLess Demo") + .with_decorations(false) + }) + }); +} + +fn app (cx: Scope) -> Element { + let desktop = cx.consume_context::().unwrap(); + cx.render(rsx!( + div { + style: "background-color: black; height: 20px; width: 100%", + onmousedown: move |_| desktop.drag_window(), + } + )) +} \ No newline at end of file diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 48c82f5a8..3a48879c0 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,100 +1,28 @@ -use std::cell::RefCell; +use wry::application::event_loop::EventLoopProxy; -use dioxus::prelude::Scope; -use dioxus_core as dioxus; -use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties}; -use dioxus_core_macro::Props; +use crate::UserWindowEvent; -/* -This module provides a set of Dioxus components to easily manage windows, tabs, etc. +type ProxyType = EventLoopProxy; -Windows can be created anywhere in the tree, making them very flexible for things like modals, etc. - -*/ -pub struct DesktopContext {} +#[derive(Clone)] +pub struct DesktopContext { + proxy: ProxyType, +} impl DesktopContext { - fn add_window(&mut self) { - // + pub fn new(proxy: ProxyType) -> Self { + Self { proxy } } - fn close_window(&mut self) { - // + + pub fn drag_window(&self) { + let _ = self.proxy.send_event(UserWindowEvent::DragWindow); } -} -enum WindowHandlers { - Resized(Box), - Moved(Box), - CloseRequested(Box), - Destroyed(Box), - DroppedFile(Box), - HoveredFile(Box), - HoverFileCancelled(Box), - ReceivedTimeText(Box), - Focused(Box), -} + pub fn minimized(&self, minimized: bool) { + let _ = self.proxy.send_event(UserWindowEvent::Minimized(minimized)); + } -#[derive(Props)] -pub struct WebviewWindowProps<'a> { - onclose: &'a dyn FnMut(()), - - onopen: &'a dyn FnMut(()), - - /// focuse me - onfocused: &'a dyn FnMut(()), - - children: Element, -} - -/// A handle to a -/// -/// -/// -/// -/// -/// -/// -/// -/// -pub fn WebviewWindow(cx: Scope) -> Element { - let dtcx = cx.consume_state::>()?; - - cx.use_hook(|_| {}); - - // render the children directly - todo!() - // cx.render(LazyNodes::new(move |f: NodeFactory| { - // f.fragment_from_iter(cx.children()) - // })) -} - -pub struct WindowHandle {} - -/// Get a handle to the current window from inside a component -pub fn use_current_window(cx: Scope) -> Option { - todo!() -} - -#[test] -fn syntax_works() { - use dioxus_core as dioxus; - use dioxus_core::prelude::*; - use dioxus_core_macro::*; - use dioxus_hooks::*; - use dioxus_html as dioxus_elements; - - static App: Component = |cx| { - cx.render(rsx! { - // left window - WebviewWindow { - onclose: move |evt| {} - onopen: move |evt| {} - onfocused: move |evt| {} - - div { - - } - } - }) - }; -} + pub fn maximized(&self, maximized: bool) { + let _ = self.proxy.send_event(UserWindowEvent::Maximized(maximized)); + } +} \ No newline at end of file diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 36b017077..b27ef7742 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -51,10 +51,12 @@ //! Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! pub mod cfg; +pub mod desktop_context; pub mod escape; pub mod events; use cfg::DesktopConfig; +use desktop_context::DesktopContext; use dioxus_core::*; use std::{ collections::{HashMap, VecDeque}, @@ -282,6 +284,27 @@ pub fn launch_with_props( // match _evt { UserWindowEvent::Update => desktop.try_load_ready_webviews(), + UserWindowEvent::DragWindow => { + // this loop just run once, because dioxus-desktop is unsupport multi-window. + for webview in desktop.webviews.values() { + let window = webview.window(); + window.drag_window().unwrap(); + } + } + UserWindowEvent::Minimized(state) => { + // this loop just run once, because dioxus-desktop is unsupport multi-window. + for webview in desktop.webviews.values() { + let window = webview.window(); + window.set_minimized(state); + } + } + UserWindowEvent::Maximized(state) => { + // this loop just run once, because dioxus-desktop is unsupport multi-window. + for webview in desktop.webviews.values() { + let window = webview.window(); + window.set_maximized(state); + } + } } } Event::MainEventsCleared => {} @@ -296,6 +319,9 @@ pub fn launch_with_props( pub enum UserWindowEvent { Update, + DragWindow, + Minimized(bool), + Maximized(bool), } pub struct DesktopController { @@ -322,6 +348,7 @@ impl DesktopController { let return_sender = sender.clone(); let proxy = evt.clone(); + let desktop_context_proxy = proxy.clone(); std::thread::spawn(move || { // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads let runtime = tokio::runtime::Builder::new_multi_thread() @@ -333,6 +360,10 @@ impl DesktopController { let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver)); + let window_context = DesktopContext::new(desktop_context_proxy); + + dom.base_scope().provide_context(window_context); + let edits = dom.rebuild(); edit_queue From 31a2f8f63abbbcc39c1ce23fb0d47d8e2e010f6f Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 31 Jan 2022 01:06:43 +0800 Subject: [PATCH 037/103] feat: add `DesktopContext` --- packages/desktop/src/desktop_context.rs | 31 +++++++++++++++++++++---- packages/desktop/src/lib.rs | 19 +++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 3a48879c0..577e33c54 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -4,25 +4,48 @@ use crate::UserWindowEvent; type ProxyType = EventLoopProxy; +/// Desktop-Window handle api context +/// +/// you can use this context control some window event +/// +/// you can use `cx.consume_context::` to get this context +/// +/// ```rust +/// let desktop = cx.consume_context::().unwrap(); +/// ``` #[derive(Clone)] pub struct DesktopContext { proxy: ProxyType, } impl DesktopContext { + pub fn new(proxy: ProxyType) -> Self { Self { proxy } } + /// trigger the drag-window event + /// + /// Moves the window with the left mouse button until the button is released. + /// + /// you need use it in `onmousedown` event: + /// ```rust + /// onmousedown: move |_| { desktop.drag_window(); } + /// ``` pub fn drag_window(&self) { let _ = self.proxy.send_event(UserWindowEvent::DragWindow); } - pub fn minimized(&self, minimized: bool) { - let _ = self.proxy.send_event(UserWindowEvent::Minimized(minimized)); + pub fn minimize(&self, minimized: bool) { + let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized)); } - pub fn maximized(&self, maximized: bool) { - let _ = self.proxy.send_event(UserWindowEvent::Maximized(maximized)); + pub fn maximize(&self, maximized: bool) { + let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized)); } + + pub fn close(&self) { + let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); + } + } \ No newline at end of file diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index b27ef7742..45314df34 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -288,20 +288,28 @@ pub fn launch_with_props( // this loop just run once, because dioxus-desktop is unsupport multi-window. for webview in desktop.webviews.values() { let window = webview.window(); - window.drag_window().unwrap(); + // start to drag the window. + // if the drag_window have any err. we don't do anything. + let _ = window.drag_window(); } } - UserWindowEvent::Minimized(state) => { + UserWindowEvent::CloseWindow => { + // close window + *control_flow = ControlFlow::Exit; + } + UserWindowEvent::Minimize(state) => { // this loop just run once, because dioxus-desktop is unsupport multi-window. for webview in desktop.webviews.values() { let window = webview.window(); + // change window minimized state. window.set_minimized(state); } } - UserWindowEvent::Maximized(state) => { + UserWindowEvent::Maximize(state) => { // this loop just run once, because dioxus-desktop is unsupport multi-window. for webview in desktop.webviews.values() { let window = webview.window(); + // change window maximized state. window.set_maximized(state); } } @@ -320,8 +328,9 @@ pub fn launch_with_props( pub enum UserWindowEvent { Update, DragWindow, - Minimized(bool), - Maximized(bool), + CloseWindow, + Minimize(bool), + Maximize(bool), } pub struct DesktopController { From d8ee553eaccd72ab68ff4dfe650e654bbd99bdf1 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sun, 30 Jan 2022 11:44:56 -0600 Subject: [PATCH 038/103] Fix the license field in Cargo.toml to be valid SPDX --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e0fbc94b6..468518f5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.8" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" From 6537ad37b867cbf8367a22294da8948feba735cb Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sun, 30 Jan 2022 11:50:57 -0600 Subject: [PATCH 039/103] Add actual copies of the relevant licenses --- LICENSE | 1 - LICENSE-APACHE | 176 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 23 +++++++ 3 files changed, 199 insertions(+), 1 deletion(-) delete mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8dbc080b8..000000000 --- a/LICENSE +++ /dev/null @@ -1 +0,0 @@ -MIT/Apache-2 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..1b5ec8b78 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..31aa79387 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. From 3edf3e367f8ac7fb813dc3826e780e2a1125b1d8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 30 Jan 2022 14:08:03 -0500 Subject: [PATCH 040/103] wip: debugging --- packages/core/src/diff.rs | 59 ++++++++++-- packages/core/src/nodes.rs | 18 +++- packages/core/src/scopes.rs | 113 +++++++++++----------- packages/core/src/virtual_dom.rs | 4 +- packages/core/tests/miri_stress.rs | 115 +++++++++++++++++++---- packages/core/tests/sharedstate.rs | 96 ++++++++++++++++++- packages/router/src/components/link.rs | 2 +- packages/router/src/components/route.rs | 4 +- packages/router/src/components/router.rs | 1 + packages/router/src/platform/mod.rs | 7 +- packages/router/src/service.rs | 76 ++++++++------- 11 files changed, 367 insertions(+), 128 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 499d9543e..c8e2b29a7 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -443,6 +443,13 @@ impl<'bump> DiffState<'bump> { new_idx }; + log::info!( + "created component {:?} with parent {:?} and originator {:?}", + new_idx, + parent_idx, + vcomponent.originator + ); + // Actually initialize the caller's slot with the right address vcomponent.scope.set(Some(new_idx)); @@ -476,6 +483,11 @@ impl<'bump> DiffState<'bump> { // Check the most common cases first // these are *actual* elements, not wrappers around lists (Text(old), Text(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - text are the sames"); + return; + } + if let Some(root) = old.id.get() { if old.text != new.text { self.mutations.set_text(new.text, root.as_u64()); @@ -487,24 +499,46 @@ impl<'bump> DiffState<'bump> { } (Placeholder(old), Placeholder(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - placeholder are the sames"); + return; + } + if let Some(root) = old.id.get() { self.scopes.update_node(new_node, root); new.id.set(Some(root)) } } - (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node), + (Element(old), Element(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - element are the sames"); + return; + } + self.diff_element_nodes(old, new, old_node, new_node) + } // These two sets are pointers to nodes but are not actually nodes themselves (Component(old), Component(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - placeholder are the sames"); + return; + } self.diff_component_nodes(old_node, new_node, *old, *new) } - (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), + (Fragment(old), Fragment(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - fragment are the sames"); + return; + } + self.diff_fragment_nodes(old, new) + } // The normal pathway still works, but generates slightly weird instructions // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove (Placeholder(_), Fragment(new)) => { + log::debug!("replacing placeholder with fragment {:?}", new_node); self.stack .create_children(new.children, MountType::Replace { old: old_node }); } @@ -686,7 +720,11 @@ impl<'bump> DiffState<'bump> { new: &'bump VComponent<'bump>, ) { let scope_addr = old.scope.get().unwrap(); - log::trace!("diff_component_nodes: {:?}", scope_addr); + log::trace!( + "diff_component_nodes. old: {:#?} new: {:#?}", + old_node, + new_node + ); if std::ptr::eq(old, new) { log::trace!("skipping component diff - component is the sames"); @@ -747,6 +785,8 @@ impl<'bump> DiffState<'bump> { self.stack.scope_stack.pop(); } else { + // + log::debug!("scope stack is {:#?}", self.stack.scope_stack); self.stack .create_node(new_node, MountType::Replace { old: old_node }); } @@ -790,7 +830,10 @@ impl<'bump> DiffState<'bump> { match (old, new) { ([], []) => {} ([], _) => self.stack.create_children(new, MountType::Append), - (_, []) => self.remove_nodes(old, true), + (_, []) => { + log::debug!("removing nodes {:?}", old); + self.remove_nodes(old, true) + } _ => { let new_is_keyed = new[0].key().is_some(); let old_is_keyed = old[0].key().is_some(); @@ -1216,6 +1259,7 @@ impl<'bump> DiffState<'bump> { } fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) { + log::debug!("Replacing node {:?}", old); match old { VNode::Element(el) => { let id = old @@ -1262,11 +1306,12 @@ impl<'bump> DiffState<'bump> { ) { // or cache the vec on the diff machine for node in nodes { + log::debug!("removing {:?}", node); match node { VNode::Text(t) => { // this check exists because our null node will be removed but does not have an ID if let Some(id) = t.id.get() { - self.scopes.collect_garbage(id); + // self.scopes.collect_garbage(id); if gen_muts { self.mutations.remove(id.as_u64()); @@ -1275,7 +1320,7 @@ impl<'bump> DiffState<'bump> { } VNode::Placeholder(a) => { let id = a.id.get().unwrap(); - self.scopes.collect_garbage(id); + // self.scopes.collect_garbage(id); if gen_muts { self.mutations.remove(id.as_u64()); @@ -1289,6 +1334,8 @@ impl<'bump> DiffState<'bump> { } self.remove_nodes(e.children, false); + + // self.scopes.collect_garbage(id); } VNode::Fragment(f) => { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index b18aeaf2d..64da46279 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{Element, Properties, Scope, ScopeId, ScopeState}, + innerlude::{Element, FcSlot, Properties, Scope, ScopeId, ScopeState}, lazynodes::LazyNodes, AnyEvent, Component, }; @@ -177,11 +177,19 @@ impl Debug for VNode<'_> { .field("children", &el.children) .finish(), VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text), - VNode::Placeholder(_) => write!(s, "VNode::VPlaceholder"), + VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id), VNode::Fragment(frag) => { write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) } - VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc), + VNode::Component(comp) => { + s.debug_struct("VNode::VComponent") + .field("fnptr", &comp.user_fc) + .field("key", &comp.key) + .field("scope", &comp.scope) + .field("originator", &comp.originator) + .finish() + //write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc) + } } } } @@ -384,7 +392,7 @@ pub struct VComponent<'src> { pub originator: ScopeId, pub scope: Cell>, pub can_memoize: bool, - pub user_fc: *const (), + pub user_fc: FcSlot, pub props: RefCell>>, } @@ -557,7 +565,7 @@ impl<'a> NodeFactory<'a> { key: key.map(|f| self.raw_text(f).0), scope: Default::default(), can_memoize: P::IS_STATIC, - user_fc: component as *const (), + user_fc: component as *mut std::os::raw::c_void, originator: self.scope.scope_id(), props: RefCell::new(Some(Box::new(VComponentProps { // local_props: RefCell::new(Some(props)), diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 77c648d18..d8cfd6505 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -13,7 +13,9 @@ use std::{ rc::Rc, }; -pub(crate) type FcSlot = *const (); +/// for traceability, we use the raw fn pointer to identify the function +/// we can use this with the traceback crate to resolve funciton names +pub(crate) type FcSlot = *mut std::os::raw::c_void; pub(crate) struct Heuristic { hook_arena_size: usize, @@ -82,7 +84,7 @@ impl ScopeArena { pub(crate) fn new_with_key( &self, - fc_ptr: *const (), + fc_ptr: FcSlot, vcomp: Box, parent_scope: Option, container: ElementId, @@ -116,25 +118,47 @@ impl ScopeArena { scope.subtree.set(subtree); scope.our_arena_idx = new_scope_id; scope.container = container; + scope.fnptr = fc_ptr; let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope); debug_assert!(any_item.is_none()); } else { // else create a new scope + let (node_capacity, hook_capacity) = self + .heuristics + .borrow() + .get(&fc_ptr) + .map(|h| (h.node_arena_size, h.hook_arena_size)) + .unwrap_or_default(); + self.scopes.borrow_mut().insert( new_scope_id, - self.bump.alloc(ScopeState::new( - height, + self.bump.alloc(ScopeState { container, - new_scope_id, + our_arena_idx: new_scope_id, parent_scope, - vcomp, - self.tasks.clone(), - self.heuristics - .borrow() - .get(&fc_ptr) - .map(|h| (h.node_arena_size, h.hook_arena_size)) - .unwrap_or_default(), - )), + height, + fnptr: fc_ptr, + props: RefCell::new(Some(vcomp)), + frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)], + + // todo: subtrees + subtree: Cell::new(0), + is_subtree_root: Cell::new(false), + + generation: 0.into(), + + tasks: self.tasks.clone(), + shared_contexts: Default::default(), + + items: RefCell::new(SelfReferentialItems { + listeners: Default::default(), + borrowed_props: Default::default(), + }), + + hook_arena: Bump::new(), + hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)), + hook_idx: Default::default(), + }), ); } @@ -169,10 +193,17 @@ impl ScopeArena { pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) { let node = unsafe { extend_vnode(node) }; - *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node; + + let mut nodes = self.nodes.borrow_mut(); + let entry = nodes.get_mut(id.0); + match entry { + Some(_node) => *_node = node, + None => panic!("cannot update node {}", id), + } } pub fn collect_garbage(&self, id: ElementId) { + log::debug!("collecting garbage for {:?}", id); self.nodes.borrow_mut().remove(id.0); } @@ -189,7 +220,7 @@ impl ScopeArena { /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// be dropped. pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { - log::trace!("Ensuring drop safety for scope {:?}", scope_id); + // log::trace!("Ensuring drop safety for scope {:?}", scope_id); if let Some(scope) = self.get_scope(scope_id) { let mut items = scope.items.borrow_mut(); @@ -217,12 +248,23 @@ impl ScopeArena { // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it. // Remove all the outdated listeners - log::trace!("Running scope {:?}", id); self.ensure_drop_safety(id); // todo: we *know* that this is aliased by the contents of the scope itself let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") }; + // if cfg!(debug_assertions) { + log::debug!("running scope {:?} symbol: {:?}", id, scope.fnptr); + + // todo: resolve frames properly + backtrace::resolve(scope.fnptr, |symbol| { + // backtrace::resolve(scope.fnptr as *mut std::os::raw::c_void, |symbol| { + // panic!("asd"); + // log::trace!("Running scope {:?}, ptr {:?}", id, scope.fnptr); + log::debug!("running scope {:?} symbol: {:?}", id, symbol.name()); + }); + // } + // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held // - All children nodes that rely on &mut T are replaced with a new reference @@ -421,6 +463,7 @@ pub struct ScopeState { pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, + pub(crate) fnptr: FcSlot, // todo: subtrees pub(crate) is_subtree_root: Cell, @@ -449,43 +492,6 @@ pub struct SelfReferentialItems<'a> { // Public methods exposed to libraries and components impl ScopeState { - fn new( - height: u32, - container: ElementId, - our_arena_idx: ScopeId, - parent_scope: Option<*mut ScopeState>, - vcomp: Box, - tasks: Rc, - (node_capacity, hook_capacity): (usize, usize), - ) -> Self { - ScopeState { - container, - our_arena_idx, - parent_scope, - height, - props: RefCell::new(Some(vcomp)), - frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)], - - // todo: subtrees - subtree: Cell::new(0), - is_subtree_root: Cell::new(false), - - generation: 0.into(), - - tasks, - shared_contexts: Default::default(), - - items: RefCell::new(SelfReferentialItems { - listeners: Default::default(), - borrowed_props: Default::default(), - }), - - hook_arena: Bump::new(), - hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)), - hook_idx: Default::default(), - } - } - /// Get the subtree ID that this scope belongs to. /// /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route @@ -731,6 +737,7 @@ impl ScopeState { while let Some(parent_ptr) = search_parent { // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; + log::trace!("Searching parent scope {:?}", parent.scope_id()); if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { return Some(shared.clone().downcast::().unwrap()); } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 4e426caad..1f443db86 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -212,7 +212,7 @@ impl VirtualDom { let scopes = ScopeArena::new(channel.0.clone()); scopes.new_with_key( - root as *const _, + root as *mut std::os::raw::c_void, Box::new(VComponentProps { props: root_props, memo: |_a, _b| unreachable!("memo on root will neve be run"), @@ -475,6 +475,8 @@ impl VirtualDom { let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); diff_state.stack.push(DiffInstruction::Diff { new, old }); + + log::debug!("pushing scope {:?} onto scope stack", scopeid); diff_state.stack.scope_stack.push(scopeid); let scope = scopes.get_scope(scopeid).unwrap(); diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 6b7805261..0f4f64546 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -319,36 +319,117 @@ fn test_pass_thru() { #[inline_props] fn Router<'a>(cx: Scope, children: Element<'a>) -> Element { cx.render(rsx! { - &cx.props.children + div { + &cx.props.children + } }) } - fn MemoizedThing(cx: Scope) -> Element { - rsx!(cx, div { "memoized" }) + #[inline_props] + fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element { + cx.render(rsx! { + header { + nav { + &cx.props.children + } + } + }) + } + + fn NavMenu(cx: Scope) -> Element { + rsx!(cx, + NavBrand {} + div { + NavStart {} + NavEnd {} + } + ) + } + + fn NavBrand(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + fn NavStart(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + fn NavEnd(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + #[inline_props] + fn MainContainer<'a>( + cx: Scope, + nav: Element<'a>, + body: Element<'a>, + footer: Element<'a>, + ) -> Element { + cx.render(rsx! { + div { + class: "columns is-mobile", + div { + class: "column is-full", + &cx.props.nav, + &cx.props.body, + &cx.props.footer, + } + } + }) } fn app(cx: Scope) -> Element { - let thing = cx.use_hook(|_| "asd"); - rsx!(cx, - Router { - MemoizedThing { - } + let nav = cx.render(rsx! { + NavContainer { + NavMenu {} } - ) + }); + let body = cx.render(rsx! { + div {} + }); + let footer = cx.render(rsx! { + div {} + }); + + cx.render(rsx! { + MainContainer { + nav: nav, + body: body, + footer: footer, + } + }) } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); - dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); - dom.work_with_deadline(|| false); + for x in 0..40 { + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); - dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); - dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); + dom.work_with_deadline(|| false); - dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); - dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); + dom.work_with_deadline(|| false); - dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); - dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); + dom.work_with_deadline(|| false); + } } diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 3f04aa154..41b178793 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -1,6 +1,6 @@ #![allow(unused, non_upper_case_globals)] -use dioxus::{prelude::*, DomEdit, Mutations}; +use dioxus::{prelude::*, DomEdit, Mutations, SchedulerMsg, ScopeId}; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; @@ -37,3 +37,97 @@ fn shared_state_test() { ] ); } + +#[test] +fn swap_test() { + struct MySharedState(&'static str); + + fn app(cx: Scope) -> Element { + let val = cx.use_hook(|_| 0); + *val += 1; + + cx.provide_context(MySharedState("world!")); + + let child = match *val % 2 { + 0 => rsx!( + Child1 { + Child1 { } + Child2 { } + } + ), + _ => rsx!( + Child2 { + Child2 { } + Child2 { } + } + ), + }; + + cx.render(rsx!( + Router { + div { child } + } + )) + } + + #[inline_props] + fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> { + cx.render(rsx!(div { children })) + } + + #[inline_props] + fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element { + let shared = cx.consume_context::().unwrap(); + println!("Child1: {}", shared.0); + cx.render(rsx! { + div { + "{shared.0}", + children + } + }) + } + + #[inline_props] + fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element { + let shared = cx.consume_context::().unwrap(); + println!("Child2: {}", shared.0); + cx.render(rsx! { + h1 { + "{shared.0}", + children + } + }) + } + + let mut dom = VirtualDom::new(app); + let Mutations { edits, .. } = dom.rebuild(); + + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + dom.work_with_deadline(|| false); + + // dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); + // dom.work_with_deadline(|| false); + + // dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); + // dom.work_with_deadline(|| false); + + // dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); + // dom.work_with_deadline(|| false); + + // dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + // dom.work_with_deadline(|| false); + // dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + // dom.work_with_deadline(|| false); + // dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); + // dom.work_with_deadline(|| false); +} diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index fd8eea66d..18e9fd92e 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -41,7 +41,7 @@ pub struct LinkProps<'a> { } pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - log::trace!("render Link to {}", cx.props.to); + // log::trace!("render Link to {}", cx.props.to); if let Some(service) = cx.consume_context::() { return cx.render(rsx! { a { diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 129953eea..e4dd2f9c8 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -30,7 +30,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { Some(ctx) => ctx.total_route.to_string(), None => cx.props.to.to_string(), }; - log::trace!("total route for {} is {}", cx.props.to, total_route); + // log::trace!("total route for {} is {}", cx.props.to, total_route); // provide our route context let route_context = cx.provide_context(RouteContext { @@ -48,7 +48,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { Some(RouteInner {}) }); - log::trace!("Checking route {}", cx.props.to); + // log::trace!("Checking route {}", cx.props.to); if router_root.should_render(cx.scope_id()) { cx.render(rsx!(&cx.props.children)) diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 240c3806a..bf23645f5 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -17,6 +17,7 @@ pub struct RouterProps<'a> { #[allow(non_snake_case)] pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element { + log::debug!("running router {:?}", cx.scope_id()); let svc = cx.use_hook(|_| { let update = cx.schedule_update_any(); cx.provide_context(RouterService::new(update, cx.scope_id())) diff --git a/packages/router/src/platform/mod.rs b/packages/router/src/platform/mod.rs index 7afc1316a..ec59a4703 100644 --- a/packages/router/src/platform/mod.rs +++ b/packages/router/src/platform/mod.rs @@ -1,9 +1,4 @@ pub trait RouterProvider { fn get_current_route(&self) -> String; - fn subscribe_to_route_changes(&self, callback: Box); -} - -enum RouteChange { - LinkTo(String), - Back(String), + fn listen(&self, callback: Box); } diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index b62bc78d5..b6d8ba11b 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -7,14 +7,18 @@ use std::{ use dioxus_core::ScopeId; +use crate::platform::RouterProvider; + pub struct RouterService { pub(crate) regen_route: Rc, pub(crate) pending_events: Rc>>, - history: Rc>, slots: Rc>>, onchange_listeners: Rc>>, root_found: Rc>>, cur_path_params: Rc>>, + + // history: Rc, + history: Rc>, listener: HistoryListener, } @@ -58,12 +62,12 @@ impl RouterService { root_found.set(None); // checking if the route is valid is cheap, so we do it for (slot, root) in slots.borrow_mut().iter().rev() { - log::trace!("regenerating slot {:?} for root '{}'", slot, root); + // log::trace!("regenerating slot {:?} for root '{}'", slot, root); regen_route(*slot); } for listener in onchange_listeners.borrow_mut().iter() { - log::trace!("regenerating listener {:?}", listener); + // log::trace!("regenerating listener {:?}", listener); regen_route(*listener); } @@ -87,31 +91,31 @@ impl RouterService { } pub fn push_route(&self, route: &str) { - log::trace!("Pushing route: {}", route); + // log::trace!("Pushing route: {}", route); self.history.borrow_mut().push(route); } pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) { let clean = clean_route(route); - log::trace!("Registered route '{}' with scope id {:?}", clean, scope); + // log::trace!("Registered route '{}' with scope id {:?}", clean, scope); self.slots.borrow_mut().push((scope, clean)); } pub fn should_render(&self, scope: ScopeId) -> bool { - log::trace!("Should render scope id {:?}?", scope); + // log::trace!("Should render scope id {:?}?", scope); if let Some(root_id) = self.root_found.get() { - log::trace!(" we already found a root with scope id {:?}", root_id); + // log::trace!(" we already found a root with scope id {:?}", root_id); if root_id == scope { - log::trace!(" yes - it's a match"); + // log::trace!(" yes - it's a match"); return true; } - log::trace!(" no - it's not a match"); + // log::trace!(" no - it's not a match"); return false; } let location = self.history.borrow().location(); let path = location.path(); - log::trace!(" current path is '{}'", path); + // log::trace!(" current path is '{}'", path); let roots = self.slots.borrow(); @@ -120,23 +124,23 @@ impl RouterService { // fallback logic match root { Some((id, route)) => { - log::trace!( - " matched given scope id {:?} with route root '{}'", - scope, - route, - ); + // log::trace!( + // " matched given scope id {:?} with route root '{}'", + // scope, + // route, + // ); if let Some(params) = route_matches_path(route, path) { - log::trace!(" and it matches the current path '{}'", path); + // log::trace!(" and it matches the current path '{}'", path); self.root_found.set(Some(*id)); *self.cur_path_params.borrow_mut() = params; true } else { if route == "" { - log::trace!(" and the route is the root, so we will use that without a better match"); + // log::trace!(" and the route is the root, so we will use that without a better match"); self.root_found.set(Some(*id)); true } else { - log::trace!(" and the route '{}' is not the root nor does it match the current path", route); + // log::trace!(" and the route '{}' is not the root nor does it match the current path", route); false } } @@ -154,12 +158,12 @@ impl RouterService { } pub fn subscribe_onchange(&self, id: ScopeId) { - log::trace!("Subscribing onchange for scope id {:?}", id); + // log::trace!("Subscribing onchange for scope id {:?}", id); self.onchange_listeners.borrow_mut().insert(id); } pub fn unsubscribe_onchange(&self, id: ScopeId) { - log::trace!("Subscribing onchange for scope id {:?}", id); + // log::trace!("Subscribing onchange for scope id {:?}", id); self.onchange_listeners.borrow_mut().remove(&id); } } @@ -182,36 +186,36 @@ fn route_matches_path(route: &str, path: &str) -> Option let route_pieces = route.split('/').collect::>(); let path_pieces = clean_path(path).split('/').collect::>(); - log::trace!( - " checking route pieces {:?} vs path pieces {:?}", - route_pieces, - path_pieces, - ); + // log::trace!( + // " checking route pieces {:?} vs path pieces {:?}", + // route_pieces, + // path_pieces, + // ); if route_pieces.len() != path_pieces.len() { - log::trace!(" the routes are different lengths"); + // log::trace!(" the routes are different lengths"); return None; } let mut matches = HashMap::new(); for (i, r) in route_pieces.iter().enumerate() { - log::trace!(" checking route piece '{}' vs path", r); + // log::trace!(" checking route piece '{}' vs path", r); // If this is a parameter then it matches as long as there's // _any_thing in that spot in the path. if r.starts_with(':') { - log::trace!( - " route piece '{}' starts with a colon so it matches anything", - r, - ); + // log::trace!( + // " route piece '{}' starts with a colon so it matches anything", + // r, + // ); let param = &r[1..]; matches.insert(param.to_string(), path_pieces[i].to_string()); continue; } - log::trace!( - " route piece '{}' must be an exact match for path piece '{}'", - r, - path_pieces[i], - ); + // log::trace!( + // " route piece '{}' must be an exact match for path piece '{}'", + // r, + // path_pieces[i], + // ); if path_pieces[i] != *r { return None; } From 3bb5c8142cf63489ffd0ff0c316cc168852d5c4f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 30 Jan 2022 17:47:58 -0500 Subject: [PATCH 041/103] fix: switch to future based diffing this commit removes the old manual fiber implementation in favor of a traditional recursion based approach. This should make the diffing algorithm easier to work on and eliminate various stack-based bugs in. --- packages/core/.rustfmt.toml | 1 + packages/core/src/diff.rs | 21 +- packages/core/src/diff_async.rs | 1102 ++++++++++++++++++++++++++++++ packages/core/src/lib.rs | 6 +- packages/core/src/mutations.rs | 8 + packages/core/src/nodes.rs | 59 +- packages/core/src/scopes.rs | 32 +- packages/core/src/virtual_dom.rs | 104 +-- packages/core/tests/diffing.rs | 257 ++----- 9 files changed, 1268 insertions(+), 322 deletions(-) create mode 100644 packages/core/.rustfmt.toml create mode 100644 packages/core/src/diff_async.rs diff --git a/packages/core/.rustfmt.toml b/packages/core/.rustfmt.toml new file mode 100644 index 000000000..70a9483a8 --- /dev/null +++ b/packages/core/.rustfmt.toml @@ -0,0 +1 @@ +struct_lit_width = 80 diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index c8e2b29a7..3b9c7692c 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -222,7 +222,7 @@ impl<'bump> DiffStack<'bump> { self.nodes_created_stack.pop().unwrap() } - fn current_scope(&self) -> Option { + pub fn current_scope(&self) -> Option { self.scope_stack.last().copied() } @@ -314,9 +314,10 @@ impl<'bump> DiffState<'bump> { } MountType::Append => { - self.mutations.edits.push(AppendChildren { - many: nodes_created as u32, - }); + self.mutations.append_children(nodes_created as u32); + // self.mutations.edits.push(AppendChildren { + // many: nodes_created as u32, + // }); } MountType::InsertAfter { other_node } => { @@ -470,7 +471,8 @@ impl<'bump> DiffState<'bump> { self.stack.create_component(new_idx, nextnode); // Finally, insert this scope as a seen node. - self.mutations.dirty_scopes.insert(new_idx); + self.mutations.mark_dirty_scope(new_idx); + // self.mutations.dirty_scopes.insert(new_idx); } // ================================= @@ -639,9 +641,10 @@ impl<'bump> DiffState<'bump> { } if old.children.is_empty() && !new.children.is_empty() { - self.mutations.edits.push(PushRoot { - root: root.as_u64(), - }); + self.mutations.push_root(root); + // self.mutations.edits.push(PushRoot { + // root: root.as_u64(), + // }); self.stack.element_stack.push(root); self.stack.instructions.push(DiffInstruction::PopElement); self.stack.create_children(new.children, MountType::Append); @@ -771,7 +774,7 @@ impl<'bump> DiffState<'bump> { // this should auto drop the previous props self.scopes.run_scope(scope_addr); - self.mutations.dirty_scopes.insert(scope_addr); + self.mutations.mark_dirty_scope(scope_addr); self.diff_node( self.scopes.wip_head(scope_addr), diff --git a/packages/core/src/diff_async.rs b/packages/core/src/diff_async.rs new file mode 100644 index 000000000..e5d63ffb5 --- /dev/null +++ b/packages/core/src/diff_async.rs @@ -0,0 +1,1102 @@ +use crate::innerlude::*; +use fxhash::{FxHashMap, FxHashSet}; +use smallvec::{smallvec, SmallVec}; + +pub(crate) struct AsyncDiffState<'bump> { + pub(crate) scopes: &'bump ScopeArena, + pub(crate) mutations: Mutations<'bump>, + pub(crate) force_diff: bool, + pub(crate) element_stack: SmallVec<[ElementId; 10]>, + pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, +} + +impl<'b> AsyncDiffState<'b> { + pub fn new(scopes: &'b ScopeArena) -> Self { + Self { + scopes, + mutations: Mutations::new(), + force_diff: false, + element_stack: smallvec![], + scope_stack: smallvec![], + } + } + + pub fn diff_scope(&mut self, scopeid: ScopeId) { + let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); + self.scope_stack.push(scopeid); + let scope = self.scopes.get_scope(scopeid).unwrap(); + self.element_stack.push(scope.container); + self.diff_node(old, new); + } + + pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { + use VNode::*; + match (old_node, new_node) { + // Check the most common cases first + // these are *actual* elements, not wrappers around lists + (Text(old), Text(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - text are the sames"); + return; + } + + if let Some(root) = old.id.get() { + if old.text != new.text { + self.mutations.set_text(new.text, root.as_u64()); + } + self.scopes.update_node(new_node, root); + + new.id.set(Some(root)); + } + } + + (Placeholder(old), Placeholder(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - placeholder are the sames"); + return; + } + + if let Some(root) = old.id.get() { + self.scopes.update_node(new_node, root); + new.id.set(Some(root)) + } + } + + (Element(old), Element(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - element are the sames"); + return; + } + self.diff_element_nodes(old, new, old_node, new_node) + } + + // These two sets are pointers to nodes but are not actually nodes themselves + (Component(old), Component(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - placeholder are the sames"); + return; + } + self.diff_component_nodes(old_node, new_node, *old, *new) + } + + (Fragment(old), Fragment(new)) => { + if std::ptr::eq(old, new) { + log::trace!("skipping node diff - fragment are the sames"); + return; + } + self.diff_fragment_nodes(old, new) + } + + // The normal pathway still works, but generates slightly weird instructions + // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove + (Placeholder(_), Fragment(_)) => { + log::debug!("replacing placeholder with fragment {:?}", new_node); + self.replace_node(old_node, new_node); + } + + // Anything else is just a basic replace and create + ( + Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), + Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), + ) => self.replace_node(old_node, new_node), + } + } + + pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize { + match node { + VNode::Text(vtext) => self.create_text_node(vtext, node), + VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node), + VNode::Element(element) => self.create_element_node(element, node), + VNode::Fragment(frag) => self.create_fragment_node(frag), + VNode::Component(component) => self.create_component_node(*component), + } + } + + fn create_text_node(&mut self, vtext: &'b VText<'b>, node: &'b VNode<'b>) -> usize { + let real_id = self.scopes.reserve_node(node); + self.mutations.create_text_node(vtext.text, real_id); + vtext.id.set(Some(real_id)); + + 1 + } + + fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize { + let real_id = self.scopes.reserve_node(node); + self.mutations.create_placeholder(real_id); + anchor.id.set(Some(real_id)); + + 1 + } + + fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize { + let VElement { + tag: tag_name, + listeners, + attributes, + children, + namespace, + id: dom_id, + parent: parent_id, + .. + } = element; + + // set the parent ID for event bubbling + // self.stack.instructions.push(DiffInstruction::PopElement); + + let parent = self.element_stack.last().unwrap(); + parent_id.set(Some(*parent)); + + // set the id of the element + let real_id = self.scopes.reserve_node(node); + self.element_stack.push(real_id); + dom_id.set(Some(real_id)); + + self.mutations.create_element(tag_name, *namespace, real_id); + + if let Some(cur_scope_id) = self.current_scope() { + for listener in *listeners { + listener.mounted_node.set(Some(real_id)); + self.mutations.new_event_listener(listener, cur_scope_id); + } + } else { + log::warn!("create element called with no scope on the stack - this is an error for a live dom"); + } + + for attr in *attributes { + self.mutations.set_attribute(attr, real_id.as_u64()); + } + + if !children.is_empty() { + self.create_and_append_children(children); + } + + 1 + } + + fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize { + self.create_children(frag.children) + } + + fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { + let parent_idx = self.current_scope().unwrap(); + + // the component might already exist - if it does, we need to reuse it + // this makes figure out when to drop the component more complicated + let new_idx = if let Some(idx) = vcomponent.scope.get() { + assert!(self.scopes.get_scope(idx).is_some()); + idx + } else { + // Insert a new scope into our component list + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = unsafe { std::mem::transmute(props) }; + let new_idx = self.scopes.new_with_key( + vcomponent.user_fc, + props, + Some(parent_idx), + self.element_stack.last().copied().unwrap(), + 0, + ); + + new_idx + }; + + log::info!( + "created component {:?} with parent {:?} and originator {:?}", + new_idx, + parent_idx, + vcomponent.originator + ); + + // Actually initialize the caller's slot with the right address + vcomponent.scope.set(Some(new_idx)); + + match vcomponent.can_memoize { + true => { + // todo: implement promotion logic. save us from boxing props that we don't need + } + false => { + // track this component internally so we know the right drop order + } + } + + // Run the scope for one iteration to initialize it + self.scopes.run_scope(new_idx); + + self.mutations.mark_dirty_scope(new_idx); + + // self.stack.create_component(new_idx, nextnode); + // Finally, insert this scope as a seen node. + + // Take the node that was just generated from running the component + let nextnode = self.scopes.fin_head(new_idx); + self.create_node(nextnode) + } + + fn diff_element_nodes( + &mut self, + old: &'b VElement<'b>, + new: &'b VElement<'b>, + old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + ) { + let root = old.id.get().unwrap(); + + // If the element type is completely different, the element needs to be re-rendered completely + // This is an optimization React makes due to how users structure their code + // + // This case is rather rare (typically only in non-keyed lists) + if new.tag != old.tag || new.namespace != old.namespace { + self.replace_node(old_node, new_node); + return; + } + + self.scopes.update_node(new_node, root); + + new.id.set(Some(root)); + new.parent.set(old.parent.get()); + + // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the + // element to modify its attributes. + // it would result in fewer instructions if we just set the id directly. + // it would also clean up this code some, but that's not very important anyways + + // Diff Attributes + // + // It's extraordinarily rare to have the number/order of attributes change + // In these cases, we just completely erase the old set and make a new set + // + // TODO: take a more efficient path than this + if old.attributes.len() == new.attributes.len() { + for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) { + if old_attr.value != new_attr.value || new_attr.is_volatile { + self.mutations.set_attribute(new_attr, root.as_u64()); + } + } + } else { + for attribute in old.attributes { + self.mutations.remove_attribute(attribute, root.as_u64()); + } + for attribute in new.attributes { + self.mutations.set_attribute(attribute, root.as_u64()) + } + } + + // Diff listeners + // + // It's extraordinarily rare to have the number/order of listeners change + // In the cases where the listeners change, we completely wipe the data attributes and add new ones + // + // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) + // + // TODO: take a more efficient path than this + if let Some(cur_scope_id) = self.current_scope() { + if old.listeners.len() == new.listeners.len() { + for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { + if old_l.event != new_l.event { + self.mutations + .remove_event_listener(old_l.event, root.as_u64()); + self.mutations.new_event_listener(new_l, cur_scope_id); + } + new_l.mounted_node.set(old_l.mounted_node.get()); + } + } else { + for listener in old.listeners { + self.mutations + .remove_event_listener(listener.event, root.as_u64()); + } + for listener in new.listeners { + listener.mounted_node.set(Some(root)); + self.mutations.new_event_listener(listener, cur_scope_id); + } + } + } + + match (old.children.len(), new.children.len()) { + (0, 0) => {} + (0, _) => { + let created = self.create_children(new.children); + self.mutations.append_children(created as u32); + } + (_, _) => { + self.diff_children(old.children, new.children); + } + }; + } + + fn diff_component_nodes( + &mut self, + old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + old: &'b VComponent<'b>, + new: &'b VComponent<'b>, + ) { + let scope_addr = old.scope.get().unwrap(); + log::trace!( + "diff_component_nodes. old: {:#?} new: {:#?}", + old_node, + new_node + ); + + if std::ptr::eq(old, new) { + log::trace!("skipping component diff - component is the sames"); + return; + } + + // Make sure we're dealing with the same component (by function pointer) + if old.user_fc == new.user_fc { + self.enter_scope(scope_addr); + + // Make sure the new component vnode is referencing the right scope id + new.scope.set(Some(scope_addr)); + + // make sure the component's caller function is up to date + let scope = self + .scopes + .get_scope(scope_addr) + .unwrap_or_else(|| panic!("could not find {:?}", scope_addr)); + + // take the new props out regardless + // when memoizing, push to the existing scope if memoization happens + let new_props = new.props.borrow_mut().take().unwrap(); + + let should_run = { + if old.can_memoize { + let props_are_the_same = unsafe { + scope + .props + .borrow() + .as_ref() + .unwrap() + .memoize(new_props.as_ref()) + }; + !props_are_the_same || self.force_diff + } else { + true + } + }; + + if should_run { + let _old_props = scope + .props + .replace(unsafe { std::mem::transmute(Some(new_props)) }); + + // this should auto drop the previous props + self.scopes.run_scope(scope_addr); + self.mutations.mark_dirty_scope(scope_addr); + + self.diff_node( + self.scopes.wip_head(scope_addr), + self.scopes.fin_head(scope_addr), + ); + } else { + log::trace!("memoized"); + // memoization has taken place + drop(new_props); + }; + + self.leave_scope(); + } else { + log::debug!("scope stack is {:#?}", self.scope_stack); + self.replace_node(old_node, new_node); + } + } + + fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { + // This is the case where options or direct vnodes might be used. + // In this case, it's faster to just skip ahead to their diff + if old.children.len() == 1 && new.children.len() == 1 { + self.diff_node(&old.children[0], &new.children[0]); + return; + } + + debug_assert!(!old.children.is_empty()); + debug_assert!(!new.children.is_empty()); + + self.diff_children(old.children, new.children); + } + + // ============================================= + // Utilities for creating new diff instructions + // ============================================= + + // Diff the given set of old and new children. + // + // The parent must be on top of the change list stack when this function is + // entered: + // + // [... parent] + // + // the change list stack is in the same state when this function returns. + // + // If old no anchors are provided, then it's assumed that we can freely append to the parent. + // + // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements. + // + // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only + // to an element, and appending makes sense. + fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + // Remember, fragments can never be empty (they always have a single child) + match (old, new) { + ([], []) => {} + ([], _) => self.create_and_append_children(new), + (_, []) => self.remove_nodes(old, true), + _ => { + let new_is_keyed = new[0].key().is_some(); + let old_is_keyed = old[0].key().is_some(); + + debug_assert!( + new.iter().all(|n| n.key().is_some() == new_is_keyed), + "all siblings must be keyed or all siblings must be non-keyed" + ); + debug_assert!( + old.iter().all(|o| o.key().is_some() == old_is_keyed), + "all siblings must be keyed or all siblings must be non-keyed" + ); + + if new_is_keyed && old_is_keyed { + self.diff_keyed_children(old, new); + } else { + self.diff_non_keyed_children(old, new); + } + } + } + } + + // Diff children that are not keyed. + // + // The parent must be on the top of the change list stack when entering this + // function: + // + // [... parent] + // + // the change list stack is in the same state when this function returns. + fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + // Handled these cases in `diff_children` before calling this function. + debug_assert!(!new.is_empty()); + debug_assert!(!old.is_empty()); + + use std::cmp::Ordering; + match old.len().cmp(&new.len()) { + Ordering::Greater => self.remove_nodes(&old[new.len()..], true), + Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), + Ordering::Equal => {} + } + + for (new, old) in new.iter().zip(old.iter()) { + self.diff_node(old, new); + } + } + + // Diffing "keyed" children. + // + // With keyed children, we care about whether we delete, move, or create nodes + // versus mutate existing nodes in place. Presumably there is some sort of CSS + // transition animation that makes the virtual DOM diffing algorithm + // observable. By specifying keys for nodes, we know which virtual DOM nodes + // must reuse (or not reuse) the same physical DOM nodes. + // + // This is loosely based on Inferno's keyed patching implementation. However, we + // have to modify the algorithm since we are compiling the diff down into change + // list instructions that will be executed later, rather than applying the + // changes to the DOM directly as we compare virtual DOMs. + // + // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 + // + // The stack is empty upon entry. + fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + if cfg!(debug_assertions) { + let mut keys = fxhash::FxHashSet::default(); + let mut assert_unique_keys = |children: &'b [VNode<'b>]| { + keys.clear(); + for child in children { + let key = child.key(); + debug_assert!( + key.is_some(), + "if any sibling is keyed, all siblings must be keyed" + ); + keys.insert(key); + } + debug_assert_eq!( + children.len(), + keys.len(), + "keyed siblings must each have a unique key" + ); + }; + assert_unique_keys(old); + assert_unique_keys(new); + } + + // First up, we diff all the nodes with the same key at the beginning of the + // children. + // + // `shared_prefix_count` is the count of how many nodes at the start of + // `new` and `old` share the same keys. + let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) { + Some(count) => count, + None => return, + }; + + // Ok, we now hopefully have a smaller range of children in the middle + // within which to re-order nodes with the same keys, remove old nodes with + // now-unused keys, and create new nodes with fresh keys. + + let old_middle = &old[left_offset..(old.len() - right_offset)]; + let new_middle = &new[left_offset..(new.len() - right_offset)]; + + debug_assert!( + !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), + "keyed children must have the same number of children" + ); + + if new_middle.is_empty() { + // remove the old elements + self.remove_nodes(old_middle, true); + } else if old_middle.is_empty() { + // there were no old elements, so just create the new elements + // we need to find the right "foothold" though - we shouldn't use the "append" at all + if left_offset == 0 { + // insert at the beginning of the old list + let foothold = &old[old.len() - right_offset]; + self.create_and_insert_before(new_middle, foothold); + } else if right_offset == 0 { + // insert at the end the old list + let foothold = old.last().unwrap(); + self.create_and_insert_after(new_middle, foothold); + } else { + // inserting in the middle + let foothold = &old[left_offset - 1]; + self.create_and_insert_after(new_middle, foothold); + } + } else { + self.diff_keyed_middle(old_middle, new_middle); + } + } + + /// Diff both ends of the children that share keys. + /// + /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing. + /// + /// If there is no offset, then this function returns None and the diffing is complete. + fn diff_keyed_ends( + &mut self, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + ) -> Option<(usize, usize)> { + let mut left_offset = 0; + + for (old, new) in old.iter().zip(new.iter()) { + // abort early if we finally run into nodes with different keys + if old.key() != new.key() { + break; + } + self.diff_node(old, new); + left_offset += 1; + } + + // If that was all of the old children, then create and append the remaining + // new children and we're finished. + if left_offset == old.len() { + self.create_and_insert_after(&new[left_offset..], old.last().unwrap()); + return None; + } + + // And if that was all of the new children, then remove all of the remaining + // old children and we're finished. + if left_offset == new.len() { + self.remove_nodes(&old[left_offset..], true); + return None; + } + + // if the shared prefix is less than either length, then we need to walk backwards + let mut right_offset = 0; + for (old, new) in old.iter().rev().zip(new.iter().rev()) { + // abort early if we finally run into nodes with different keys + if old.key() != new.key() { + break; + } + self.diff_node(old, new); + right_offset += 1; + } + + Some((left_offset, right_offset)) + } + + // The most-general, expensive code path for keyed children diffing. + // + // We find the longest subsequence within `old` of children that are relatively + // ordered the same way in `new` (via finding a longest-increasing-subsequence + // of the old child's index within `new`). The children that are elements of + // this subsequence will remain in place, minimizing the number of DOM moves we + // will have to do. + // + // Upon entry to this function, the change list stack must be empty. + // + // This function will load the appropriate nodes onto the stack and do diffing in place. + // + // Upon exit from this function, it will be restored to that same self. + fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + /* + 1. Map the old keys into a numerical ordering based on indices. + 2. Create a map of old key to its index + 3. Map each new key to the old key, carrying over the old index. + - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3 + - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist + + now, we should have a list of integers that indicates where in the old list the new items map to. + + 4. Compute the LIS of this list + - this indicates the longest list of new children that won't need to be moved. + + 5. Identify which nodes need to be removed + 6. Identify which nodes will need to be diffed + + 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS. + - if the item already existed, just move it to the right place. + + 8. Finally, generate instructions to remove any old children. + 9. Generate instructions to finally diff children that are the same between both + */ + + // 0. Debug sanity checks + // Should have already diffed the shared-key prefixes and suffixes. + debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key())); + debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key())); + + // 1. Map the old keys into a numerical ordering based on indices. + // 2. Create a map of old key to its index + // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3). + let old_key_to_old_index = old + .iter() + .enumerate() + .map(|(i, o)| (o.key().unwrap(), i)) + .collect::>(); + + let mut shared_keys = FxHashSet::default(); + + // 3. Map each new key to the old key, carrying over the old index. + let new_index_to_old_index = new + .iter() + .map(|node| { + let key = node.key().unwrap(); + if let Some(&index) = old_key_to_old_index.get(&key) { + shared_keys.insert(key); + index + } else { + u32::MAX as usize + } + }) + .collect::>(); + + // If none of the old keys are reused by the new children, then we remove all the remaining old children and + // create the new children afresh. + if shared_keys.is_empty() { + if let Some(first_old) = old.get(0) { + self.remove_nodes(&old[1..], true); + let nodes_created = self.create_children(new); + self.replace_inner(first_old, nodes_created); + } else { + // I think this is wrong - why are we appending? + // only valid of the if there are no trailing elements + self.create_and_append_children(new); + } + return; + } + + // 4. Compute the LIS of this list + let mut lis_sequence = Vec::default(); + lis_sequence.reserve(new_index_to_old_index.len()); + + let mut predecessors = vec![0; new_index_to_old_index.len()]; + let mut starts = vec![0; new_index_to_old_index.len()]; + + longest_increasing_subsequence::lis_with( + &new_index_to_old_index, + &mut lis_sequence, + |a, b| a < b, + &mut predecessors, + &mut starts, + ); + + // the lis comes out backwards, I think. can't quite tell. + lis_sequence.sort_unstable(); + + // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) + if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { + lis_sequence.pop(); + } + + for idx in lis_sequence.iter() { + self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]); + } + + let mut nodes_created = 0; + + // add mount instruction for the first items not covered by the lis + let last = *lis_sequence.last().unwrap(); + if last < (new.len() - 1) { + for (idx, new_node) in new[(last + 1)..].iter().enumerate() { + let new_idx = idx + last + 1; + let old_index = new_index_to_old_index[new_idx]; + if old_index == u32::MAX as usize { + nodes_created += self.create_node(new_node); + } else { + self.diff_node(&old[old_index], new_node); + nodes_created += self.push_all_nodes(new_node); + } + } + + self.mutations.insert_after( + self.find_last_element(&new[last]).unwrap(), + nodes_created as u32, + ); + nodes_created = 0; + } + + // for each spacing, generate a mount instruction + let mut lis_iter = lis_sequence.iter().rev(); + let mut last = *lis_iter.next().unwrap(); + for next in lis_iter { + if last - next > 1 { + for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { + let new_idx = idx + next + 1; + + let old_index = new_index_to_old_index[new_idx]; + if old_index == u32::MAX as usize { + nodes_created += self.create_node(new_node); + } else { + self.diff_node(&old[old_index], new_node); + nodes_created += self.push_all_nodes(new_node); + } + } + + self.mutations.insert_before( + self.find_first_element(&new[last]).unwrap(), + nodes_created as u32, + ); + + nodes_created = 0; + } + last = *next; + } + + // add mount instruction for the last items not covered by the lis + let first_lis = *lis_sequence.first().unwrap(); + if first_lis > 0 { + for (idx, new_node) in new[..first_lis].iter().enumerate() { + let old_index = new_index_to_old_index[idx]; + if old_index == u32::MAX as usize { + nodes_created += self.create_node(new_node); + } else { + self.diff_node(&old[old_index], new_node); + nodes_created += self.push_all_nodes(new_node); + } + } + + self.mutations.insert_before( + self.find_first_element(&new[first_lis]).unwrap(), + nodes_created as u32, + ); + } + } + + fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { + // fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { + log::debug!("Replacing node {:?}", old); + let nodes_created = self.create_node(new); + self.replace_inner(old, nodes_created); + } + + fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) { + match old { + VNode::Element(el) => { + let id = old + .try_mounted_id() + .unwrap_or_else(|| panic!("broke on {:?}", old)); + + self.mutations.replace_with(id, nodes_created as u32); + self.remove_nodes(el.children, false); + self.scopes.collect_garbage(id); + } + + VNode::Text(_) | VNode::Placeholder(_) => { + let id = old + .try_mounted_id() + .unwrap_or_else(|| panic!("broke on {:?}", old)); + + self.mutations.replace_with(id, nodes_created as u32); + self.scopes.collect_garbage(id); + } + + VNode::Fragment(f) => { + self.replace_inner(&f.children[0], nodes_created); + self.remove_nodes(f.children.iter().skip(1), true); + } + + VNode::Component(c) => { + let node = self.scopes.fin_head(c.scope.get().unwrap()); + self.replace_node(node, node); + + let scope_id = c.scope.get().unwrap(); + + // we can only remove components if they are actively being diffed + if self.scope_stack.contains(&c.originator) { + self.scopes.try_remove(scope_id).unwrap(); + } + } + } + } + + pub fn remove_nodes(&mut self, nodes: impl IntoIterator>, gen_muts: bool) { + for node in nodes { + match node { + VNode::Text(t) => { + // this check exists because our null node will be removed but does not have an ID + if let Some(id) = t.id.get() { + self.scopes.collect_garbage(id); + + if gen_muts { + self.mutations.remove(id.as_u64()); + } + } + } + VNode::Placeholder(a) => { + let id = a.id.get().unwrap(); + self.scopes.collect_garbage(id); + + if gen_muts { + self.mutations.remove(id.as_u64()); + } + } + VNode::Element(e) => { + let id = e.id.get().unwrap(); + + if gen_muts { + self.mutations.remove(id.as_u64()); + } + + self.scopes.collect_garbage(id); + + self.remove_nodes(e.children, false); + } + + VNode::Fragment(f) => { + self.remove_nodes(f.children, gen_muts); + } + + VNode::Component(c) => { + let scope_id = c.scope.get().unwrap(); + let root = self.scopes.root_node(scope_id); + self.remove_nodes([root], gen_muts); + + // we can only remove this node if the originator is actively + if self.scope_stack.contains(&c.originator) { + self.scopes.try_remove(scope_id).unwrap(); + } + } + } + } + } + + fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize { + let mut created = 0; + for node in nodes { + created += self.create_node(node); + } + created + } + + fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) { + let created = self.create_children(nodes); + self.mutations.append_children(created as u32); + } + + fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) { + let created = self.create_children(nodes); + let last = self.find_last_element(after).unwrap(); + self.mutations.insert_after(last, created as u32); + } + + fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) { + let created = self.create_children(nodes); + let first = self.find_first_element(before).unwrap(); + self.mutations.insert_before(first, created as u32); + } + + fn current_scope(&self) -> Option { + self.scope_stack.last().copied() + } + + fn enter_scope(&mut self, scope: ScopeId) { + self.scope_stack.push(scope); + } + + fn leave_scope(&mut self) { + self.scope_stack.pop(); + } + + fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option { + let mut search_node = Some(vnode); + + loop { + match &search_node.take().unwrap() { + VNode::Text(t) => break t.id.get(), + VNode::Element(t) => break t.id.get(), + VNode::Placeholder(t) => break t.id.get(), + VNode::Fragment(frag) => { + search_node = frag.children.last(); + } + VNode::Component(el) => { + let scope_id = el.scope.get().unwrap(); + search_node = Some(self.scopes.root_node(scope_id)); + } + } + } + } + + fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option { + let mut search_node = Some(vnode); + + loop { + match &search_node.take().unwrap() { + // the ones that have a direct id + VNode::Fragment(frag) => { + search_node = Some(&frag.children[0]); + } + VNode::Component(el) => { + let scope_id = el.scope.get().unwrap(); + search_node = Some(self.scopes.root_node(scope_id)); + } + VNode::Text(t) => break t.id.get(), + VNode::Element(t) => break t.id.get(), + VNode::Placeholder(t) => break t.id.get(), + } + } + } + + // fn replace_node(&mut self, old: &'b VNode<'b>, nodes_created: usize) { + // log::debug!("Replacing node {:?}", old); + // match old { + // VNode::Element(el) => { + // let id = old + // .try_mounted_id() + // .unwrap_or_else(|| panic!("broke on {:?}", old)); + + // self.mutations.replace_with(id, nodes_created as u32); + // self.remove_nodes(el.children, false); + // } + + // VNode::Text(_) | VNode::Placeholder(_) => { + // let id = old + // .try_mounted_id() + // .unwrap_or_else(|| panic!("broke on {:?}", old)); + + // self.mutations.replace_with(id, nodes_created as u32); + // } + + // VNode::Fragment(f) => { + // self.replace_node(&f.children[0], nodes_created); + // self.remove_nodes(f.children.iter().skip(1), true); + // } + + // VNode::Component(c) => { + // let node = self.scopes.fin_head(c.scope.get().unwrap()); + // self.replace_node(node, nodes_created); + + // let scope_id = c.scope.get().unwrap(); + + // // we can only remove components if they are actively being diffed + // if self.stack.scope_stack.contains(&c.originator) { + // self.scopes.try_remove(scope_id).unwrap(); + // } + // } + // } + // } + + // /// schedules nodes for garbage collection and pushes "remove" to the mutation stack + // /// remove can happen whenever + // pub(crate) fn remove_nodes( + // &mut self, + // nodes: impl IntoIterator>, + // gen_muts: bool, + // ) { + // // or cache the vec on the diff machine + // for node in nodes { + // log::debug!("removing {:?}", node); + // match node { + // VNode::Text(t) => { + // // this check exists because our null node will be removed but does not have an ID + // if let Some(id) = t.id.get() { + // // self.scopes.collect_garbage(id); + + // if gen_muts { + // self.mutations.remove(id.as_u64()); + // } + // } + // } + // VNode::Placeholder(a) => { + // let id = a.id.get().unwrap(); + // // self.scopes.collect_garbage(id); + + // if gen_muts { + // self.mutations.remove(id.as_u64()); + // } + // } + // VNode::Element(e) => { + // let id = e.id.get().unwrap(); + + // if gen_muts { + // self.mutations.remove(id.as_u64()); + // } + + // self.remove_nodes(e.children, false); + + // // self.scopes.collect_garbage(id); + // } + + // VNode::Fragment(f) => { + // self.remove_nodes(f.children, gen_muts); + // } + + // VNode::Component(c) => { + // let scope_id = c.scope.get().unwrap(); + // let root = self.scopes.root_node(scope_id); + // self.remove_nodes(Some(root), gen_muts); + + // // we can only remove this node if the originator is actively + // if self.stack.scope_stack.contains(&c.originator) { + // self.scopes.try_remove(scope_id).unwrap(); + // } + // } + // } + // } + // } + + // recursively push all the nodes of a tree onto the stack and return how many are there + fn push_all_nodes(&mut self, node: &'b VNode<'b>) -> usize { + match node { + VNode::Text(_) | VNode::Placeholder(_) => { + self.mutations.push_root(node.mounted_id()); + 1 + } + + VNode::Fragment(_) | VNode::Component(_) => { + // + let mut added = 0; + for child in node.children() { + added += self.push_all_nodes(child); + } + added + } + + VNode::Element(el) => { + let mut num_on_stack = 0; + for child in el.children.iter() { + num_on_stack += self.push_all_nodes(child); + } + self.mutations.push_root(el.id.get().unwrap()); + + num_on_stack + 1 + } + } + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index dba4effbb..48a8a3505 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,7 +1,8 @@ #![allow(non_snake_case)] #![doc = include_str!("../README.md")] -pub(crate) mod diff; +// pub(crate) mod diff; +pub(crate) mod diff_async; pub(crate) mod events; pub(crate) mod lazynodes; pub(crate) mod mutations; @@ -12,7 +13,8 @@ pub(crate) mod util; pub(crate) mod virtual_dom; pub(crate) mod innerlude { - pub(crate) use crate::diff::*; + pub(crate) use crate::diff_async::*; + // pub(crate) use crate::diff::*; pub use crate::events::*; pub use crate::lazynodes::*; pub use crate::mutations::*; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index fa96edd1b..bd94d0b71 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -143,6 +143,10 @@ impl<'a> Mutations<'a> { self.edits.push(InsertBefore { n, root }); } + pub(crate) fn append_children(&mut self, n: u32) { + self.edits.push(AppendChildren { many: n }); + } + // Remove Nodes from the dom pub(crate) fn remove(&mut self, id: u64) { self.edits.push(Remove { root: id }); @@ -217,6 +221,10 @@ impl<'a> Mutations<'a> { let name = attribute.name; self.edits.push(RemoveAttribute { name, root }); } + + pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) { + self.dirty_scopes.insert(scope); + } } // refs are only assigned once diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 64da46279..74234a84c 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -175,6 +175,7 @@ impl Debug for VNode<'_> { .field("key", &el.key) .field("attrs", &el.attributes) .field("children", &el.children) + .field("id", &el.id) .finish(), VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text), VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id), @@ -365,9 +366,7 @@ pub struct EventHandler<'bump, T = ()> { impl<'a, T> Default for EventHandler<'a, T> { fn default() -> Self { - Self { - callback: RefCell::new(None), - } + Self { callback: RefCell::new(None) } } } @@ -441,10 +440,7 @@ pub struct NodeFactory<'a> { impl<'a> NodeFactory<'a> { pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> { - NodeFactory { - scope, - bump: &scope.wip_frame().bump, - } + NodeFactory { scope, bump: &scope.wip_frame().bump } } #[inline] @@ -454,11 +450,10 @@ impl<'a> NodeFactory<'a> { /// Directly pass in text blocks without the need to use the format_args macro. pub fn static_text(&self, text: &'static str) -> VNode<'a> { - VNode::Text(self.bump.alloc(VText { - id: empty_cell(), - text, - is_static: true, - })) + VNode::Text( + self.bump + .alloc(VText { id: empty_cell(), text, is_static: true }), + ) } /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static @@ -481,11 +476,7 @@ impl<'a> NodeFactory<'a> { pub fn text(&self, args: Arguments) -> VNode<'a> { let (text, is_static) = self.raw_text(args); - VNode::Text(self.bump.alloc(VText { - text, - is_static, - id: empty_cell(), - })) + VNode::Text(self.bump.alloc(VText { text, is_static, id: empty_cell() })) } pub fn element( @@ -543,13 +534,7 @@ impl<'a> NodeFactory<'a> { is_volatile: bool, ) -> Attribute<'a> { let (value, is_static) = self.raw_text(val); - Attribute { - name, - value, - is_static, - namespace, - is_volatile, - } + Attribute { name, value, is_static, namespace, is_volatile } } pub fn component

( @@ -591,11 +576,7 @@ impl<'a> NodeFactory<'a> { } pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> { - Listener { - event, - mounted_node: Cell::new(None), - callback, - } + Listener { event, mounted_node: Cell::new(None), callback } } pub fn fragment_root<'b, 'c>( @@ -611,10 +592,10 @@ impl<'a> NodeFactory<'a> { if nodes.is_empty() { VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() })) } else { - VNode::Fragment(self.bump.alloc(VFragment { - children: nodes.into_bump_slice(), - key: None, - })) + VNode::Fragment( + self.bump + .alloc(VFragment { children: nodes.into_bump_slice(), key: None }), + ) } } @@ -650,10 +631,7 @@ impl<'a> NodeFactory<'a> { ); } - VNode::Fragment(self.bump.alloc(VFragment { - children, - key: None, - })) + VNode::Fragment(self.bump.alloc(VFragment { children, key: None })) } } @@ -676,10 +654,9 @@ impl<'a> NodeFactory<'a> { } else { let children = nodes.into_bump_slice(); - Some(VNode::Fragment(self.bump.alloc(VFragment { - children, - key: None, - }))) + Some(VNode::Fragment( + self.bump.alloc(VFragment { children, key: None }), + )) } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index d8cfd6505..542afc673 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -203,7 +203,8 @@ impl ScopeArena { } pub fn collect_garbage(&self, id: ElementId) { - log::debug!("collecting garbage for {:?}", id); + // println!("collecting garbage for {:?}", id); + // log::debug!("collecting garbage for {:?}", id); self.nodes.borrow_mut().remove(id.0); } @@ -304,11 +305,9 @@ impl ScopeArena { frame.node.set(unsafe { extend_vnode(node) }); } else { let frame = scope.wip_frame(); - let node = frame - .bump - .alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder { - id: Default::default(), - }))); + let node = frame.bump.alloc(VNode::Placeholder( + frame.bump.alloc(VPlaceholder { id: Default::default() }), + )); frame.node.set(unsafe { extend_vnode(node) }); } @@ -418,10 +417,7 @@ pub struct Scope<'a, P = ()> { impl

Copy for Scope<'_, P> {} impl

Clone for Scope<'_, P> { fn clone(&self) -> Self { - Self { - scope: self.scope, - props: self.props, - } + Self { scope: self.scope, props: self.props } } } @@ -784,10 +780,7 @@ impl ScopeState { /// } ///``` pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option> { - Some(rsx.call(NodeFactory { - scope: self, - bump: &self.wip_frame().bump, - })) + Some(rsx.call(NodeFactory { scope: self, bump: &self.wip_frame().bump })) } /// Store a value between renders @@ -894,10 +887,7 @@ impl ScopeState { self.shared_contexts.get_mut().clear(); // next: reset the node data - let SelfReferentialItems { - borrowed_props, - listeners, - } = self.items.get_mut(); + let SelfReferentialItems { borrowed_props, listeners } = self.items.get_mut(); borrowed_props.clear(); listeners.clear(); self.frames[0].reset(); @@ -955,11 +945,7 @@ pub(crate) struct TaskQueue { pub(crate) type InnerTask = Pin>>; impl TaskQueue { fn new(sender: UnboundedSender) -> Rc { - Rc::new(Self { - tasks: RefCell::new(FxHashMap::default()), - gen: Cell::new(0), - sender, - }) + Rc::new(Self { tasks: RefCell::new(FxHashMap::default()), gen: Cell::new(0), sender }) } fn push_fut(&self, task: impl Future + 'static) -> TaskId { let pinned = Box::pin(task); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1f443db86..d879e0340 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -2,6 +2,7 @@ //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. +use crate::diff_async::AsyncDiffState as DiffState; use crate::innerlude::*; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{future::poll_fn, StreamExt}; @@ -453,7 +454,7 @@ impl VirtualDom { while !self.dirty_scopes.is_empty() { let scopes = &self.scopes; - let mut diff_state = DiffState::new(scopes); + let mut diff_state = AsyncDiffState::new(scopes); let mut ran_scopes = FxHashSet::default(); @@ -473,32 +474,33 @@ impl VirtualDom { self.scopes.run_scope(scopeid); - let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); - diff_state.stack.push(DiffInstruction::Diff { new, old }); + diff_state.diff_scope(scopeid); - log::debug!("pushing scope {:?} onto scope stack", scopeid); - diff_state.stack.scope_stack.push(scopeid); + let AsyncDiffState { mutations, .. } = diff_state; - let scope = scopes.get_scope(scopeid).unwrap(); - diff_state.stack.element_stack.push(scope.container); + for scope in &mutations.dirty_scopes { + self.dirty_scopes.remove(scope); + } + + committed_mutations.push(mutations); + + // if diff_state.work(&mut deadline) { + // let DiffState { mutations, .. } = diff_state; + + // for scope in &mutations.dirty_scopes { + // self.dirty_scopes.remove(scope); + // } + + // committed_mutations.push(mutations); + // } else { + // // leave the work in an incomplete state + // // + // // todo: we should store the edits and re-apply them later + // // for now, we just dump the work completely (threadsafe) + // return committed_mutations; + // } } } - - if diff_state.work(&mut deadline) { - let DiffState { mutations, .. } = diff_state; - - for scope in &mutations.dirty_scopes { - self.dirty_scopes.remove(scope); - } - - committed_mutations.push(mutations); - } else { - // leave the work in an incomplete state - // - // todo: we should store the edits and re-apply them later - // for now, we just dump the work completely (threadsafe) - return committed_mutations; - } } committed_mutations @@ -526,13 +528,15 @@ impl VirtualDom { let mut diff_state = DiffState::new(&self.scopes); self.scopes.run_scope(scope_id); - diff_state - .stack - .create_node(self.scopes.fin_head(scope_id), MountType::Append); - diff_state.stack.element_stack.push(ElementId(0)); - diff_state.stack.scope_stack.push(scope_id); - diff_state.work(|| false); + diff_state.element_stack.push(ElementId(0)); + diff_state.scope_stack.push(scope_id); + + let node = self.scopes.fin_head(scope_id); + let created = diff_state.create_node(node); + + diff_state.mutations.append_children(created as u32); + self.dirty_scopes.clear(); assert!(self.dirty_scopes.is_empty()); @@ -579,12 +583,11 @@ impl VirtualDom { ); diff_machine.force_diff = true; - diff_machine.stack.push(DiffInstruction::Diff { old, new }); - diff_machine.stack.scope_stack.push(scope_id); - + diff_machine.scope_stack.push(scope_id); let scope = diff_machine.scopes.get_scope(scope_id).unwrap(); - diff_machine.stack.element_stack.push(scope.container); - diff_machine.work(|| false); + diff_machine.element_stack.push(scope.container); + + diff_machine.diff_node(old, new); diff_machine.mutations } @@ -623,10 +626,10 @@ impl VirtualDom { /// ``` pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { let mut machine = DiffState::new(&self.scopes); - machine.stack.push(DiffInstruction::Diff { new, old }); - machine.stack.element_stack.push(ElementId(0)); - machine.stack.scope_stack.push(ScopeId(0)); - machine.work(|| false); + machine.element_stack.push(ElementId(0)); + machine.scope_stack.push(ScopeId(0)); + machine.diff_node(old, new); + machine.mutations } @@ -645,11 +648,11 @@ impl VirtualDom { /// ``` pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> { let mut machine = DiffState::new(&self.scopes); - machine.stack.element_stack.push(ElementId(0)); - machine - .stack - .create_node(self.render_vnodes(nodes), MountType::Append); - machine.work(|| false); + machine.scope_stack.push(ScopeId(0)); + machine.element_stack.push(ElementId(0)); + let node = self.render_vnodes(nodes); + let created = machine.create_node(node); + machine.mutations.append_children(created as u32); machine.mutations } @@ -674,16 +677,15 @@ impl VirtualDom { let (old, new) = (self.render_vnodes(left), self.render_vnodes(right)); let mut create = DiffState::new(&self.scopes); - create.stack.scope_stack.push(ScopeId(0)); - create.stack.element_stack.push(ElementId(0)); - create.stack.create_node(old, MountType::Append); - create.work(|| false); + create.scope_stack.push(ScopeId(0)); + create.element_stack.push(ElementId(0)); + let created = create.create_node(old); + create.mutations.append_children(created as u32); let mut edit = DiffState::new(&self.scopes); - edit.stack.scope_stack.push(ScopeId(0)); - edit.stack.element_stack.push(ElementId(0)); - edit.stack.push(DiffInstruction::Diff { old, new }); - edit.work(|| false); + edit.scope_stack.push(ScopeId(0)); + edit.element_stack.push(ElementId(0)); + edit.diff_node(old, new); (create.mutations, edit.mutations) } diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 5b98ea80e..c407359c9 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -1,4 +1,5 @@ #![allow(unused, non_upper_case_globals)] + //! Diffing Tests //! //! These tests only verify that the diffing algorithm works properly for single components. @@ -31,26 +32,14 @@ fn html_and_rsx_generate_the_same_output() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "Hello world" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "Hello world" }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, ] ); - assert_eq!( - change.edits, - [SetText { - text: "Goodbye world", - root: 2 - },] - ); + assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]); } /// Should result in 3 elements on the stack @@ -67,32 +56,14 @@ fn fragments_create_properly() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "Hello a" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "Hello a" }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "div" - }, - CreateTextNode { - root: 4, - text: "Hello b" - }, + CreateElement { root: 3, tag: "div" }, + CreateTextNode { root: 4, text: "Hello b" }, AppendChildren { many: 1 }, - CreateElement { - root: 5, - tag: "div" - }, - CreateTextNode { - root: 6, - text: "Hello c" - }, + CreateElement { root: 5, tag: "div" }, + CreateTextNode { root: 6, text: "Hello c" }, AppendChildren { many: 1 }, AppendChildren { many: 3 }, ] @@ -116,10 +87,7 @@ fn empty_fragments_create_anchors() { assert_eq!( change.edits, [ - CreateElement { - root: 2, - tag: "div" - }, + CreateElement { root: 2, tag: "div" }, ReplaceWith { m: 1, root: 1 } ] ); @@ -138,29 +106,15 @@ fn empty_fragments_create_many_anchors() { create.edits, [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] ); + assert_eq!( change.edits, [ - CreateElement { - root: 2, - tag: "div" - }, - CreateElement { - root: 3, - tag: "div" - }, - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 2, tag: "div" }, + CreateElement { root: 3, tag: "div" }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, + CreateElement { root: 6, tag: "div" }, ReplaceWith { m: 5, root: 1 } ] ); @@ -188,32 +142,14 @@ fn empty_fragments_create_anchors_with_many_children() { assert_eq!( change.edits, [ - CreateElement { - tag: "div", - root: 2, - }, - CreateTextNode { - text: "hello: 0", - root: 3 - }, + CreateElement { tag: "div", root: 2 }, + CreateTextNode { text: "hello: 0", root: 3 }, AppendChildren { many: 1 }, - CreateElement { - tag: "div", - root: 4, - }, - CreateTextNode { - text: "hello: 1", - root: 5 - }, + CreateElement { tag: "div", root: 4 }, + CreateTextNode { text: "hello: 1", root: 5 }, AppendChildren { many: 1 }, - CreateElement { - tag: "div", - root: 6, - }, - CreateTextNode { - text: "hello: 2", - root: 7 - }, + CreateElement { tag: "div", root: 6 }, + CreateTextNode { text: "hello: 2", root: 7 }, AppendChildren { many: 1 }, ReplaceWith { root: 1, m: 3 } ] @@ -236,23 +172,11 @@ fn many_items_become_fragment() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - text: "hello", - root: 2 - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { text: "hello", root: 2 }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "div" - }, - CreateTextNode { - text: "hello", - root: 4 - }, + CreateElement { root: 3, tag: "div" }, + CreateTextNode { text: "hello", root: 4 }, AppendChildren { many: 1 }, AppendChildren { many: 2 }, ] @@ -315,7 +239,7 @@ fn two_fragments_with_differrent_elements_are_differet() { // replace the divs with new h1s CreateElement { tag: "h1", root: 7 }, ReplaceWith { root: 1, m: 1 }, - CreateElement { tag: "h1", root: 8 }, + CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used ReplaceWith { root: 2, m: 1 }, ] ); @@ -339,39 +263,27 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateElement { - root: 3, - tag: "div" - }, - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateElement { root: 3, tag: "div" }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, CreateElement { root: 6, tag: "p" }, AppendChildren { many: 6 }, ] ); + + // note: key reuse is always the last node that got used + // slab maintains a linked list, essentially assert_eq!( change.edits, [ Remove { root: 3 }, Remove { root: 4 }, Remove { root: 5 }, - CreateElement { root: 7, tag: "h1" }, - ReplaceWith { root: 1, m: 1 }, - CreateElement { root: 8, tag: "h1" }, + CreateElement { root: 5, tag: "h1" }, // 3 gets reused + ReplaceWith { root: 1, m: 1 }, // 1 gets deleted + CreateElement { root: 1, tag: "h1" }, // 1 gets reused ReplaceWith { root: 2, m: 1 }, ] ); @@ -395,14 +307,8 @@ fn two_fragments_with_same_elements_are_differet() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, CreateElement { root: 3, tag: "p" }, AppendChildren { many: 3 }, ] @@ -410,18 +316,9 @@ fn two_fragments_with_same_elements_are_differet() { assert_eq!( change.edits, [ - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, + CreateElement { root: 6, tag: "div" }, InsertAfter { root: 2, n: 3 }, ] ); @@ -628,14 +525,8 @@ fn keyed_diffing_additions() { assert_eq!( change.edits, [ - CreateElement { - root: 6, - tag: "div" - }, - CreateElement { - root: 7, - tag: "div" - }, + CreateElement { root: 6, tag: "div" }, + CreateElement { root: 7, tag: "div" }, InsertAfter { n: 2, root: 5 } ] ); @@ -663,14 +554,8 @@ fn keyed_diffing_additions_and_moves_on_ends() { change.edits, [ // create 11, 12 - CreateElement { - tag: "div", - root: 5 - }, - CreateElement { - tag: "div", - root: 6 - }, + CreateElement { tag: "div", root: 5 }, + CreateElement { tag: "div", root: 6 }, InsertAfter { root: 3, n: 2 }, // move 7 to the front PushRoot { root: 4 }, @@ -684,43 +569,32 @@ fn keyed_diffing_additions_and_moves_in_middle() { let dom = new_dom(); let left = rsx!({ - [/**/ 4, 5, 6, 7 /**/].iter().map(|f| { + [/**/ 1, 2, 3, 4 /**/].iter().map(|f| { rsx! { div { key: "{f}" }} }) }); let right = rsx!({ - [/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| { + [/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| { rsx! { div { key: "{f}" }} }) }); + // LIS: 4, 5, 6 let (_, change) = dom.diff_lazynodes(left, right); log::debug!("{:#?}", change); assert_eq!( change.edits, [ - // create 13, 17 - CreateElement { - tag: "div", - root: 5 - }, - CreateElement { - tag: "div", - root: 6 - }, - InsertBefore { root: 2, n: 2 }, - // create 11, 12 - CreateElement { - tag: "div", - root: 7 - }, - CreateElement { - tag: "div", - root: 8 - }, + // create 5, 6 + CreateElement { tag: "div", root: 5 }, + CreateElement { tag: "div", root: 6 }, InsertBefore { root: 3, n: 2 }, + // create 7, 8 + CreateElement { tag: "div", root: 7 }, + CreateElement { tag: "div", root: 8 }, + InsertBefore { root: 2, n: 2 }, // move 7 PushRoot { root: 4 }, InsertBefore { root: 1, n: 1 } @@ -756,16 +630,10 @@ fn controlled_keyed_diffing_out_of_order() { // remove 7 // create 9 and insert before 6 - CreateElement { - root: 5, - tag: "div" - }, + CreateElement { root: 5, tag: "div" }, InsertBefore { n: 1, root: 3 }, // create 0 and insert before 5 - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 6, tag: "div" }, InsertBefore { n: 1, root: 2 }, ] ); @@ -792,10 +660,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() { assert_eq!( changes.edits, [ - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 6, tag: "div" }, InsertBefore { n: 1, root: 3 }, PushRoot { root: 4 }, InsertBefore { n: 1, root: 1 }, From 1ea42799c0f63a34f4eb1208bd78b2c165eb920e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 30 Jan 2022 18:34:24 -0500 Subject: [PATCH 042/103] wip: clean up the core crate after switching to recursive diff engine --- packages/core/src/diff.rs | 1170 ++++++++++------------------- packages/core/src/diff_async.rs | 1102 --------------------------- packages/core/src/lib.rs | 6 +- packages/core/src/scopes.rs | 3 +- packages/core/src/virtual_dom.rs | 2 +- packages/core/tests/earlyabort.rs | 20 +- packages/core/tests/lifecycle.rs | 119 +-- packages/core/tests/passthru.rs | 108 +++ 8 files changed, 562 insertions(+), 1968 deletions(-) delete mode 100644 packages/core/src/diff_async.rs create mode 100644 packages/core/tests/passthru.rs diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3b9c7692c..d790292c8 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,485 +1,35 @@ -//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children. -//! -//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set -//! of mutations for the RealDom to apply. -//! -//! ## Notice: -//! -//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support -//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancellation, NodeRefs, pausing, priority -//! scheduling, and additional batching operations. -//! -//! ## Implementation Details: -//! -//! ### IDs for elements -//! -------------------- -//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes. -//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose -//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element -//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive -//! garbage collection as the VirtualDOM replaces old nodes. -//! -//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing, -//! we always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly -//! and brick the user's page. -//! -//! ### Fragment Support -//! -------------------- -//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments -//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty -//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is -//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding -//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to -//! the platform. -//! -//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is -//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children. -//! -//! ### Suspense -//! ------------ -//! Dioxus implements Suspense slightly differently than React. In React, each fiber is manually progressed until it runs -//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once -//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before -//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on. -//! Due to the frequent calls to "yield_now" we can get the pure "fetch-as-you-render" behavior of React Fiber. -//! -//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to -//! DOM, but as a placeholder. -//! -//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes -//! and contents, without the need for the "use_suspense" hook. In the interim, this is the quickest way to get Suspense working. -//! -//! ## Subtree Memoization -//! ----------------------- -//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can -//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the -//! calls to "create" propagate this information upwards. Structures like the one below are entirely static: -//! ```rust, ignore -//! rsx!( div { class: "hello world", "this node is entirely static" } ) -//! ``` -//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to -//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time -//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as -//! a way of short-circuiting the most expensive checks. -//! -//! ## Bloom Filter and Heuristics -//! ------------------------------ -//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are -//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a -//! bump arena after first render, the number of hooks, and the number of nodes in the tree. -//! -//! ## Garbage Collection -//! --------------------- -//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage -//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then -//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient. -//! -//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy -//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes -//! for the client. As new nodes are created, old nodes will be over-written. -//! -//! ## Further Reading and Thoughts -//! ---------------------------- -//! There are more ways of increasing diff performance here that are currently not implemented. -//! - Strong memoization of subtrees. -//! - Guided diffing. -//! - Certain web-dom-specific optimizations. -//! -//! More info on how to improve this diffing algorithm: -//! - - use crate::innerlude::*; use fxhash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -use DomEdit::*; -/// Our DiffState is an iterative tree differ. -/// -/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This -/// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively, -/// meaning we could not "pause" the original diffing algorithm. -/// -/// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically -/// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for -/// the next period of idle time, preventing our diff algorithm from blocking the main thread. -/// -/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's -/// stack machines all the way down! -pub(crate) struct DiffState<'bump> { +pub(crate) struct AsyncDiffState<'bump> { pub(crate) scopes: &'bump ScopeArena, pub(crate) mutations: Mutations<'bump>, - pub(crate) stack: DiffStack<'bump>, pub(crate) force_diff: bool, + pub(crate) element_stack: SmallVec<[ElementId; 10]>, + pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, } -impl<'bump> DiffState<'bump> { - pub(crate) fn new(scopes: &'bump ScopeArena) -> Self { +impl<'b> AsyncDiffState<'b> { + pub fn new(scopes: &'b ScopeArena) -> Self { Self { scopes, mutations: Mutations::new(), - stack: DiffStack::new(), force_diff: false, - } - } -} - -/// The stack instructions we use to diff and create new nodes. -#[derive(Debug)] -pub(crate) enum DiffInstruction<'a> { - Diff { - old: &'a VNode<'a>, - new: &'a VNode<'a>, - }, - - Create { - node: &'a VNode<'a>, - }, - - /// pushes the node elements onto the stack for use in mount - PrepareMove { - node: &'a VNode<'a>, - }, - - Mount { - and: MountType<'a>, - }, - - PopScope, - PopElement, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum MountType<'a> { - Absorb, - Append, - Replace { old: &'a VNode<'a> }, - InsertAfter { other_node: &'a VNode<'a> }, - InsertBefore { other_node: &'a VNode<'a> }, -} - -pub(crate) struct DiffStack<'bump> { - pub(crate) instructions: Vec>, - pub(crate) nodes_created_stack: SmallVec<[usize; 10]>, - pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, - pub(crate) element_stack: SmallVec<[ElementId; 10]>, -} - -impl<'bump> DiffStack<'bump> { - fn new() -> Self { - Self { - instructions: Vec::with_capacity(1000), - nodes_created_stack: smallvec![], - scope_stack: smallvec![], element_stack: smallvec![], + scope_stack: smallvec![], } } - fn pop(&mut self) -> Option> { - self.instructions.pop() + pub fn diff_scope(&mut self, scopeid: ScopeId) { + let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); + self.scope_stack.push(scopeid); + let scope = self.scopes.get_scope(scopeid).unwrap(); + self.element_stack.push(scope.container); + self.diff_node(old, new); } - fn pop_off_scope(&mut self) { - self.scope_stack.pop(); - } - - pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) { - self.instructions.push(instruction) - } - - fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) { - self.nodes_created_stack.push(0); - self.instructions.push(DiffInstruction::Mount { and }); - - for child in children.iter().rev() { - self.instructions - .push(DiffInstruction::Create { node: child }); - } - } - - // todo: subtrees - // fn push_subtree(&mut self) { - // self.nodes_created_stack.push(0); - // self.instructions.push(DiffInstruction::Mount { - // and: MountType::Append, - // }); - // } - - fn push_nodes_created(&mut self, count: usize) { - self.nodes_created_stack.push(count); - } - - pub(crate) fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) { - self.nodes_created_stack.push(0); - self.instructions.push(DiffInstruction::Mount { and }); - self.instructions.push(DiffInstruction::Create { node }); - } - - fn add_child_count(&mut self, count: usize) { - *self.nodes_created_stack.last_mut().unwrap() += count; - } - - fn pop_nodes_created(&mut self) -> usize { - self.nodes_created_stack.pop().unwrap() - } - - pub fn current_scope(&self) -> Option { - self.scope_stack.last().copied() - } - - fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) { - // Push the new scope onto the stack - self.scope_stack.push(idx); - - self.instructions.push(DiffInstruction::PopScope); - - // Run the creation algorithm with this scope on the stack - // ?? I think we treat components as fragments?? - self.instructions.push(DiffInstruction::Create { node }); - } -} - -impl<'bump> DiffState<'bump> { - /// Progress the diffing for this "fiber" - /// - /// This method implements a depth-first iterative tree traversal. - /// - /// We do depth-first to maintain high cache locality (nodes were originally generated recursively). - /// - /// Returns a `bool` indicating that the work completed properly. - pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool { - while let Some(instruction) = self.stack.pop() { - match instruction { - DiffInstruction::Diff { old, new } => self.diff_node(old, new), - DiffInstruction::Create { node } => self.create_node(node), - DiffInstruction::Mount { and } => self.mount(and), - DiffInstruction::PrepareMove { node } => { - let num_on_stack = self.push_all_nodes(node); - self.stack.add_child_count(num_on_stack); - } - DiffInstruction::PopScope => self.stack.pop_off_scope(), - DiffInstruction::PopElement => { - self.stack.element_stack.pop(); - } - }; - - if deadline_expired() { - log::trace!("Deadline expired before we could finish!"); - return false; - } - } - - true - } - - // recursively push all the nodes of a tree onto the stack and return how many are there - fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize { - match node { - VNode::Text(_) | VNode::Placeholder(_) => { - self.mutations.push_root(node.mounted_id()); - 1 - } - - VNode::Fragment(_) | VNode::Component(_) => { - // - let mut added = 0; - for child in node.children() { - added += self.push_all_nodes(child); - } - added - } - - VNode::Element(el) => { - let mut num_on_stack = 0; - for child in el.children.iter() { - num_on_stack += self.push_all_nodes(child); - } - self.mutations.push_root(el.id.get().unwrap()); - - num_on_stack + 1 - } - } - } - - fn mount(&mut self, and: MountType<'bump>) { - let nodes_created = self.stack.pop_nodes_created(); - match and { - // add the nodes from this virtual list to the parent - // used by fragments and components - MountType::Absorb => { - self.stack.add_child_count(nodes_created); - } - - MountType::Replace { old } => { - self.replace_node(old, nodes_created); - } - - MountType::Append => { - self.mutations.append_children(nodes_created as u32); - // self.mutations.edits.push(AppendChildren { - // many: nodes_created as u32, - // }); - } - - MountType::InsertAfter { other_node } => { - let root = self.find_last_element(other_node).unwrap(); - self.mutations.insert_after(root, nodes_created as u32); - } - - MountType::InsertBefore { other_node } => { - let root = self.find_first_element_id(other_node).unwrap(); - self.mutations.insert_before(root, nodes_created as u32); - } - } - } - - // ================================= - // Tools for creating new nodes - // ================================= - - fn create_node(&mut self, node: &'bump VNode<'bump>) { - match node { - VNode::Text(vtext) => self.create_text_node(vtext, node), - VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node), - VNode::Element(element) => self.create_element_node(element, node), - VNode::Fragment(frag) => self.create_fragment_node(frag), - VNode::Component(component) => self.create_component_node(*component), - } - } - - fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) { - let real_id = self.scopes.reserve_node(node); - - self.mutations.create_text_node(vtext.text, real_id); - vtext.id.set(Some(real_id)); - self.stack.add_child_count(1); - } - - fn create_anchor_node(&mut self, anchor: &'bump VPlaceholder, node: &'bump VNode<'bump>) { - let real_id = self.scopes.reserve_node(node); - - self.mutations.create_placeholder(real_id); - anchor.id.set(Some(real_id)); - - self.stack.add_child_count(1); - } - - fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) { - let VElement { - tag: tag_name, - listeners, - attributes, - children, - namespace, - id: dom_id, - parent: parent_id, - .. - } = element; - - // set the parent ID for event bubbling - self.stack.instructions.push(DiffInstruction::PopElement); - - let parent = self.stack.element_stack.last().unwrap(); - parent_id.set(Some(*parent)); - - // set the id of the element - let real_id = self.scopes.reserve_node(node); - self.stack.element_stack.push(real_id); - dom_id.set(Some(real_id)); - - self.mutations.create_element(tag_name, *namespace, real_id); - - self.stack.add_child_count(1); - - if let Some(cur_scope_id) = self.stack.current_scope() { - for listener in *listeners { - listener.mounted_node.set(Some(real_id)); - self.mutations.new_event_listener(listener, cur_scope_id); - } - } else { - log::warn!("create element called with no scope on the stack - this is an error for a live dom"); - } - - for attr in *attributes { - self.mutations.set_attribute(attr, real_id.as_u64()); - } - - // todo: the settext optimization - // - // if children.len() == 1 { - // if let VNode::Text(vtext) = children[0] { - // self.mutations.set_text(vtext.text, real_id.as_u64()); - // return; - // } - // } - - if !children.is_empty() { - self.stack.create_children(children, MountType::Append); - } - } - - fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { - self.stack.create_children(frag.children, MountType::Absorb); - } - - fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { - let parent_idx = self.stack.current_scope().unwrap(); - - // the component might already exist - if it does, we need to reuse it - // this makes figure out when to drop the component more complicated - let new_idx = if let Some(idx) = vcomponent.scope.get() { - assert!(self.scopes.get_scope(idx).is_some()); - idx - } else { - // Insert a new scope into our component list - let props: Box = vcomponent.props.borrow_mut().take().unwrap(); - let props: Box = unsafe { std::mem::transmute(props) }; - let new_idx = self.scopes.new_with_key( - vcomponent.user_fc, - props, - Some(parent_idx), - self.stack.element_stack.last().copied().unwrap(), - 0, - ); - - new_idx - }; - - log::info!( - "created component {:?} with parent {:?} and originator {:?}", - new_idx, - parent_idx, - vcomponent.originator - ); - - // Actually initialize the caller's slot with the right address - vcomponent.scope.set(Some(new_idx)); - - match vcomponent.can_memoize { - true => { - // todo: implement promotion logic. save us from boxing props that we don't need - } - false => { - // track this component internally so we know the right drop order - } - } - - // Run the scope for one iteration to initialize it - self.scopes.run_scope(new_idx); - - // Take the node that was just generated from running the component - let nextnode = self.scopes.fin_head(new_idx); - self.stack.create_component(new_idx, nextnode); - - // Finally, insert this scope as a seen node. - self.mutations.mark_dirty_scope(new_idx); - // self.mutations.dirty_scopes.insert(new_idx); - } - - // ================================= - // Tools for diffing nodes - // ================================= - - pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { + pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { use VNode::*; match (old_node, new_node) { // Check the most common cases first @@ -539,28 +89,157 @@ impl<'bump> DiffState<'bump> { // The normal pathway still works, but generates slightly weird instructions // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove - (Placeholder(_), Fragment(new)) => { + (Placeholder(_), Fragment(_)) => { log::debug!("replacing placeholder with fragment {:?}", new_node); - self.stack - .create_children(new.children, MountType::Replace { old: old_node }); + self.replace_node(old_node, new_node); } // Anything else is just a basic replace and create ( Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), - ) => self - .stack - .create_node(new_node, MountType::Replace { old: old_node }), + ) => self.replace_node(old_node, new_node), } } + pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize { + match node { + VNode::Text(vtext) => self.create_text_node(vtext, node), + VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node), + VNode::Element(element) => self.create_element_node(element, node), + VNode::Fragment(frag) => self.create_fragment_node(frag), + VNode::Component(component) => self.create_component_node(*component), + } + } + + fn create_text_node(&mut self, vtext: &'b VText<'b>, node: &'b VNode<'b>) -> usize { + let real_id = self.scopes.reserve_node(node); + self.mutations.create_text_node(vtext.text, real_id); + vtext.id.set(Some(real_id)); + + 1 + } + + fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize { + let real_id = self.scopes.reserve_node(node); + self.mutations.create_placeholder(real_id); + anchor.id.set(Some(real_id)); + + 1 + } + + fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize { + let VElement { + tag: tag_name, + listeners, + attributes, + children, + namespace, + id: dom_id, + parent: parent_id, + .. + } = element; + + // set the parent ID for event bubbling + // self.stack.instructions.push(DiffInstruction::PopElement); + + let parent = self.element_stack.last().unwrap(); + parent_id.set(Some(*parent)); + + // set the id of the element + let real_id = self.scopes.reserve_node(node); + self.element_stack.push(real_id); + dom_id.set(Some(real_id)); + + self.mutations.create_element(tag_name, *namespace, real_id); + + if let Some(cur_scope_id) = self.current_scope() { + for listener in *listeners { + listener.mounted_node.set(Some(real_id)); + self.mutations.new_event_listener(listener, cur_scope_id); + } + } else { + log::warn!("create element called with no scope on the stack - this is an error for a live dom"); + } + + for attr in *attributes { + self.mutations.set_attribute(attr, real_id.as_u64()); + } + + if !children.is_empty() { + self.create_and_append_children(children); + } + + 1 + } + + fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize { + self.create_children(frag.children) + } + + fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { + let parent_idx = self.current_scope().unwrap(); + + // the component might already exist - if it does, we need to reuse it + // this makes figure out when to drop the component more complicated + let new_idx = if let Some(idx) = vcomponent.scope.get() { + assert!(self.scopes.get_scope(idx).is_some()); + idx + } else { + // Insert a new scope into our component list + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = unsafe { std::mem::transmute(props) }; + let new_idx = self.scopes.new_with_key( + vcomponent.user_fc, + props, + Some(parent_idx), + self.element_stack.last().copied().unwrap(), + 0, + ); + + new_idx + }; + + log::info!( + "created component {:?} with parent {:?} and originator {:?}", + new_idx, + parent_idx, + vcomponent.originator + ); + + // Actually initialize the caller's slot with the right address + vcomponent.scope.set(Some(new_idx)); + + match vcomponent.can_memoize { + true => { + // todo: implement promotion logic. save us from boxing props that we don't need + } + false => { + // track this component internally so we know the right drop order + } + } + + self.enter_scope(new_idx); + + // Run the scope for one iteration to initialize it + self.scopes.run_scope(new_idx); + self.mutations.mark_dirty_scope(new_idx); + + // Take the node that was just generated from running the component + let nextnode = self.scopes.fin_head(new_idx); + let created = self.create_node(nextnode); + + self.leave_scope(); + + created + } + fn diff_element_nodes( &mut self, - old: &'bump VElement<'bump>, - new: &'bump VElement<'bump>, - old_node: &'bump VNode<'bump>, - new_node: &'bump VNode<'bump>, + old: &'b VElement<'b>, + new: &'b VElement<'b>, + old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, ) { let root = old.id.get().unwrap(); @@ -569,13 +248,7 @@ impl<'bump> DiffState<'bump> { // // This case is rather rare (typically only in non-keyed lists) if new.tag != old.tag || new.namespace != old.namespace { - // maybe make this an instruction? - // issue is that we need the "vnode" but this method only has the velement - self.stack.push_nodes_created(0); - self.stack.push(DiffInstruction::Mount { - and: MountType::Replace { old: old_node }, - }); - self.create_element_node(new, new_node); + self.replace_node(old_node, new_node); return; } @@ -618,7 +291,7 @@ impl<'bump> DiffState<'bump> { // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) // // TODO: take a more efficient path than this - if let Some(cur_scope_id) = self.stack.current_scope() { + if let Some(cur_scope_id) = self.current_scope() { if old.listeners.len() == new.listeners.len() { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { if old_l.event != new_l.event { @@ -640,87 +313,24 @@ impl<'bump> DiffState<'bump> { } } - if old.children.is_empty() && !new.children.is_empty() { - self.mutations.push_root(root); - // self.mutations.edits.push(PushRoot { - // root: root.as_u64(), - // }); - self.stack.element_stack.push(root); - self.stack.instructions.push(DiffInstruction::PopElement); - self.stack.create_children(new.children, MountType::Append); - } else { - self.stack.element_stack.push(root); - self.stack.instructions.push(DiffInstruction::PopElement); - self.diff_children(old.children, new.children); - } - - // todo: this is for the "settext" optimization - // it works, but i'm not sure if it's the direction we want to take right away - // I haven't benchmarked the performance imporvemenet yet. Perhaps - // we can make it a config? - - // match (old.children.len(), new.children.len()) { - // (0, 0) => {} - // (1, 1) => { - // let old1 = &old.children[0]; - // let new1 = &new.children[0]; - - // match (old1, new1) { - // (VNode::Text(old_text), VNode::Text(new_text)) => { - // if old_text.text != new_text.text { - // self.mutations.set_text(new_text.text, root.as_u64()); - // } - // } - // (VNode::Text(_old_text), _) => { - // self.stack.element_stack.push(root); - // self.stack.instructions.push(DiffInstruction::PopElement); - // self.stack.create_node(new1, MountType::Append); - // } - // (_, VNode::Text(new_text)) => { - // self.remove_nodes([old1], false); - // self.mutations.set_text(new_text.text, root.as_u64()); - // } - // _ => { - // self.stack.element_stack.push(root); - // self.stack.instructions.push(DiffInstruction::PopElement); - // self.diff_children(old.children, new.children); - // } - // } - // } - // (0, 1) => { - // if let VNode::Text(text) = &new.children[0] { - // self.mutations.set_text(text.text, root.as_u64()); - // } else { - // self.stack.element_stack.push(root); - // self.stack.instructions.push(DiffInstruction::PopElement); - // } - // } - // (0, _) => { - // self.mutations.edits.push(PushRoot { - // root: root.as_u64(), - // }); - // self.stack.element_stack.push(root); - // self.stack.instructions.push(DiffInstruction::PopElement); - // self.stack.create_children(new.children, MountType::Append); - // } - // (_, 0) => { - // self.remove_nodes(old.children, false); - // self.mutations.set_text("", root.as_u64()); - // } - // (_, _) => { - // self.stack.element_stack.push(root); - // self.stack.instructions.push(DiffInstruction::PopElement); - // self.diff_children(old.children, new.children); - // } - // } + match (old.children.len(), new.children.len()) { + (0, 0) => {} + (0, _) => { + let created = self.create_children(new.children); + self.mutations.append_children(created as u32); + } + (_, _) => { + self.diff_children(old.children, new.children); + } + }; } fn diff_component_nodes( &mut self, - old_node: &'bump VNode<'bump>, - new_node: &'bump VNode<'bump>, - old: &'bump VComponent<'bump>, - new: &'bump VComponent<'bump>, + old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + old: &'b VComponent<'b>, + new: &'b VComponent<'b>, ) { let scope_addr = old.scope.get().unwrap(); log::trace!( @@ -736,7 +346,7 @@ impl<'bump> DiffState<'bump> { // Make sure we're dealing with the same component (by function pointer) if old.user_fc == new.user_fc { - self.stack.scope_stack.push(scope_addr); + self.enter_scope(scope_addr); // Make sure the new component vnode is referencing the right scope id new.scope.set(Some(scope_addr)); @@ -786,16 +396,14 @@ impl<'bump> DiffState<'bump> { drop(new_props); }; - self.stack.scope_stack.pop(); + self.leave_scope(); } else { - // - log::debug!("scope stack is {:#?}", self.stack.scope_stack); - self.stack - .create_node(new_node, MountType::Replace { old: old_node }); + log::debug!("scope stack is {:#?}", self.scope_stack); + self.replace_node(old_node, new_node); } } - fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) { + fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { // This is the case where options or direct vnodes might be used. // In this case, it's faster to just skip ahead to their diff if old.children.len() == 1 && new.children.len() == 1 { @@ -828,15 +436,12 @@ impl<'bump> DiffState<'bump> { // // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only // to an element, and appending makes sense. - fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { + fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { // Remember, fragments can never be empty (they always have a single child) match (old, new) { ([], []) => {} - ([], _) => self.stack.create_children(new, MountType::Append), - (_, []) => { - log::debug!("removing nodes {:?}", old); - self.remove_nodes(old, true) - } + ([], _) => self.create_and_append_children(new), + (_, []) => self.remove_nodes(old, true), _ => { let new_is_keyed = new[0].key().is_some(); let old_is_keyed = old[0].key().is_some(); @@ -867,29 +472,20 @@ impl<'bump> DiffState<'bump> { // [... parent] // // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { + fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { // Handled these cases in `diff_children` before calling this function. debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); - for (new, old) in new.iter().zip(old.iter()).rev() { - self.stack.push(DiffInstruction::Diff { new, old }); - } - use std::cmp::Ordering; match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..], true), - Ordering::Less => { - self.stack.create_children( - &new[old.len()..], - MountType::InsertAfter { - other_node: old.last().unwrap(), - }, - ); - } - Ordering::Equal => { - // nothing - they're the same size - } + Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), + Ordering::Equal => {} + } + + for (new, old) in new.iter().zip(old.iter()) { + self.diff_node(old, new); } } @@ -909,10 +505,10 @@ impl<'bump> DiffState<'bump> { // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // // The stack is empty upon entry. - fn diff_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { + fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { if cfg!(debug_assertions) { let mut keys = fxhash::FxHashSet::default(); - let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| { + let mut assert_unique_keys = |children: &'b [VNode<'b>]| { keys.clear(); for child in children { let key = child.key(); @@ -953,6 +549,7 @@ impl<'bump> DiffState<'bump> { !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), "keyed children must have the same number of children" ); + if new_middle.is_empty() { // remove the old elements self.remove_nodes(old_middle, true); @@ -962,30 +559,15 @@ impl<'bump> DiffState<'bump> { if left_offset == 0 { // insert at the beginning of the old list let foothold = &old[old.len() - right_offset]; - self.stack.create_children( - new_middle, - MountType::InsertBefore { - other_node: foothold, - }, - ); + self.create_and_insert_before(new_middle, foothold); } else if right_offset == 0 { // insert at the end the old list let foothold = old.last().unwrap(); - self.stack.create_children( - new_middle, - MountType::InsertAfter { - other_node: foothold, - }, - ); + self.create_and_insert_after(new_middle, foothold); } else { // inserting in the middle let foothold = &old[left_offset - 1]; - self.stack.create_children( - new_middle, - MountType::InsertAfter { - other_node: foothold, - }, - ); + self.create_and_insert_after(new_middle, foothold); } } else { self.diff_keyed_middle(old_middle, new_middle); @@ -999,9 +581,8 @@ impl<'bump> DiffState<'bump> { /// If there is no offset, then this function returns None and the diffing is complete. fn diff_keyed_ends( &mut self, - - old: &'bump [VNode<'bump>], - new: &'bump [VNode<'bump>], + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], ) -> Option<(usize, usize)> { let mut left_offset = 0; @@ -1010,19 +591,14 @@ impl<'bump> DiffState<'bump> { if old.key() != new.key() { break; } - self.stack.push(DiffInstruction::Diff { old, new }); + self.diff_node(old, new); left_offset += 1; } // If that was all of the old children, then create and append the remaining // new children and we're finished. if left_offset == old.len() { - self.stack.create_children( - &new[left_offset..], - MountType::InsertAfter { - other_node: old.last().unwrap(), - }, - ); + self.create_and_insert_after(&new[left_offset..], old.last().unwrap()); return None; } @@ -1060,7 +636,7 @@ impl<'bump> DiffState<'bump> { // This function will load the appropriate nodes onto the stack and do diffing in place. // // Upon exit from this function, it will be restored to that same self. - fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { + fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -1118,10 +694,12 @@ impl<'bump> DiffState<'bump> { if shared_keys.is_empty() { if let Some(first_old) = old.get(0) { self.remove_nodes(&old[1..], true); - self.stack - .create_children(new, MountType::Replace { old: first_old }) + let nodes_created = self.create_children(new); + self.replace_inner(first_old, nodes_created); } else { - self.stack.create_children(new, MountType::Append {}); + // I think this is wrong - why are we appending? + // only valid of the if there are no trailing elements + self.create_and_append_children(new); } return; } @@ -1149,33 +727,31 @@ impl<'bump> DiffState<'bump> { lis_sequence.pop(); } - let apply = |new_idx, new_node: &'bump VNode<'bump>, stack: &mut DiffStack<'bump>| { - let old_index = new_index_to_old_index[new_idx]; - if old_index == u32::MAX as usize { - stack.create_node(new_node, MountType::Absorb); - } else { - // this function should never take LIS indices - stack.push(DiffInstruction::PrepareMove { node: new_node }); - stack.push(DiffInstruction::Diff { - new: new_node, - old: &old[old_index], - }); - } - }; + for idx in lis_sequence.iter() { + self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]); + } - // add mount instruction for the last items not covered by the lis - let first_lis = *lis_sequence.first().unwrap(); - if first_lis > 0 { - self.stack.push_nodes_created(0); - self.stack.push(DiffInstruction::Mount { - and: MountType::InsertBefore { - other_node: &new[first_lis], - }, - }); + let mut nodes_created = 0; - for (idx, new_node) in new[..first_lis].iter().enumerate().rev() { - apply(idx, new_node, &mut self.stack); + // add mount instruction for the first items not covered by the lis + let last = *lis_sequence.last().unwrap(); + if last < (new.len() - 1) { + for (idx, new_node) in new[(last + 1)..].iter().enumerate() { + let new_idx = idx + last + 1; + let old_index = new_index_to_old_index[new_idx]; + if old_index == u32::MAX as usize { + nodes_created += self.create_node(new_node); + } else { + self.diff_node(&old[old_index], new_node); + nodes_created += self.push_all_nodes(new_node); + } } + + self.mutations.insert_after( + self.find_last_element(&new[last]).unwrap(), + nodes_created as u32, + ); + nodes_created = 0; } // for each spacing, generate a mount instruction @@ -1183,46 +759,185 @@ impl<'bump> DiffState<'bump> { let mut last = *lis_iter.next().unwrap(); for next in lis_iter { if last - next > 1 { - self.stack.push_nodes_created(0); - self.stack.push(DiffInstruction::Mount { - and: MountType::InsertBefore { - other_node: &new[last], - }, - }); - for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() { - apply(idx + next + 1, new_node, &mut self.stack); + for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { + let new_idx = idx + next + 1; + + let old_index = new_index_to_old_index[new_idx]; + if old_index == u32::MAX as usize { + nodes_created += self.create_node(new_node); + } else { + self.diff_node(&old[old_index], new_node); + nodes_created += self.push_all_nodes(new_node); + } } + + self.mutations.insert_before( + self.find_first_element(&new[last]).unwrap(), + nodes_created as u32, + ); + + nodes_created = 0; } last = *next; } - // add mount instruction for the first items not covered by the lis - let last = *lis_sequence.last().unwrap(); - if last < (new.len() - 1) { - self.stack.push_nodes_created(0); - self.stack.push(DiffInstruction::Mount { - and: MountType::InsertAfter { - other_node: &new[last], - }, - }); - for (idx, new_node) in new[(last + 1)..].iter().enumerate().rev() { - apply(idx + last + 1, new_node, &mut self.stack); + // add mount instruction for the last items not covered by the lis + let first_lis = *lis_sequence.first().unwrap(); + if first_lis > 0 { + for (idx, new_node) in new[..first_lis].iter().enumerate() { + let old_index = new_index_to_old_index[idx]; + if old_index == u32::MAX as usize { + nodes_created += self.create_node(new_node); + } else { + self.diff_node(&old[old_index], new_node); + nodes_created += self.push_all_nodes(new_node); + } } - } - for idx in lis_sequence.iter().rev() { - self.stack.push(DiffInstruction::Diff { - new: &new[*idx], - old: &old[new_index_to_old_index[*idx]], - }); + self.mutations.insert_before( + self.find_first_element(&new[first_lis]).unwrap(), + nodes_created as u32, + ); } } - // ===================== - // Utilities - // ===================== + fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { + log::debug!("Replacing node\n old: {:?}\n new: {:?}", old, new); + let nodes_created = self.create_node(new); + self.replace_inner(old, nodes_created); + } - fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> Option { + fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) { + match old { + VNode::Element(el) => { + let id = old + .try_mounted_id() + .unwrap_or_else(|| panic!("broke on {:?}", old)); + + self.mutations.replace_with(id, nodes_created as u32); + self.remove_nodes(el.children, false); + self.scopes.collect_garbage(id); + } + + VNode::Text(_) | VNode::Placeholder(_) => { + let id = old + .try_mounted_id() + .unwrap_or_else(|| panic!("broke on {:?}", old)); + + self.mutations.replace_with(id, nodes_created as u32); + self.scopes.collect_garbage(id); + } + + VNode::Fragment(f) => { + self.replace_inner(&f.children[0], nodes_created); + self.remove_nodes(f.children.iter().skip(1), true); + } + + VNode::Component(c) => { + let node = self.scopes.fin_head(c.scope.get().unwrap()); + self.replace_inner(node, nodes_created); + + let scope_id = c.scope.get().unwrap(); + + // we can only remove components if they are actively being diffed + if self.scope_stack.contains(&c.originator) { + log::debug!("Removing component {:?}", old); + + self.scopes.try_remove(scope_id).unwrap(); + } + } + } + } + + pub fn remove_nodes(&mut self, nodes: impl IntoIterator>, gen_muts: bool) { + for node in nodes { + match node { + VNode::Text(t) => { + // this check exists because our null node will be removed but does not have an ID + if let Some(id) = t.id.get() { + self.scopes.collect_garbage(id); + + if gen_muts { + self.mutations.remove(id.as_u64()); + } + } + } + VNode::Placeholder(a) => { + let id = a.id.get().unwrap(); + self.scopes.collect_garbage(id); + + if gen_muts { + self.mutations.remove(id.as_u64()); + } + } + VNode::Element(e) => { + let id = e.id.get().unwrap(); + + if gen_muts { + self.mutations.remove(id.as_u64()); + } + + self.scopes.collect_garbage(id); + + self.remove_nodes(e.children, false); + } + + VNode::Fragment(f) => { + self.remove_nodes(f.children, gen_muts); + } + + VNode::Component(c) => { + let scope_id = c.scope.get().unwrap(); + let root = self.scopes.root_node(scope_id); + self.remove_nodes([root], gen_muts); + + // we can only remove this node if the originator is actively + if self.scope_stack.contains(&c.originator) { + self.scopes.try_remove(scope_id).unwrap(); + } + } + } + } + } + + fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize { + let mut created = 0; + for node in nodes { + created += self.create_node(node); + } + created + } + + fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) { + let created = self.create_children(nodes); + self.mutations.append_children(created as u32); + } + + fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) { + let created = self.create_children(nodes); + let last = self.find_last_element(after).unwrap(); + self.mutations.insert_after(last, created as u32); + } + + fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) { + let created = self.create_children(nodes); + let first = self.find_first_element(before).unwrap(); + self.mutations.insert_before(first, created as u32); + } + + fn current_scope(&self) -> Option { + self.scope_stack.last().copied() + } + + fn enter_scope(&mut self, scope: ScopeId) { + self.scope_stack.push(scope); + } + + fn leave_scope(&mut self) { + self.scope_stack.pop(); + } + + fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option { let mut search_node = Some(vnode); loop { @@ -1241,7 +956,7 @@ impl<'bump> DiffState<'bump> { } } - fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option { + fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option { let mut search_node = Some(vnode); loop { @@ -1261,100 +976,31 @@ impl<'bump> DiffState<'bump> { } } - fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) { - log::debug!("Replacing node {:?}", old); - match old { - VNode::Element(el) => { - let id = old - .try_mounted_id() - .unwrap_or_else(|| panic!("broke on {:?}", old)); - - self.mutations.replace_with(id, nodes_created as u32); - self.remove_nodes(el.children, false); - } - + // recursively push all the nodes of a tree onto the stack and return how many are there + fn push_all_nodes(&mut self, node: &'b VNode<'b>) -> usize { + match node { VNode::Text(_) | VNode::Placeholder(_) => { - let id = old - .try_mounted_id() - .unwrap_or_else(|| panic!("broke on {:?}", old)); - - self.mutations.replace_with(id, nodes_created as u32); + self.mutations.push_root(node.mounted_id()); + 1 } - VNode::Fragment(f) => { - self.replace_node(&f.children[0], nodes_created); - self.remove_nodes(f.children.iter().skip(1), true); + VNode::Fragment(_) | VNode::Component(_) => { + // + let mut added = 0; + for child in node.children() { + added += self.push_all_nodes(child); + } + added } - VNode::Component(c) => { - let node = self.scopes.fin_head(c.scope.get().unwrap()); - self.replace_node(node, nodes_created); - - let scope_id = c.scope.get().unwrap(); - - // we can only remove components if they are actively being diffed - if self.stack.scope_stack.contains(&c.originator) { - self.scopes.try_remove(scope_id).unwrap(); + VNode::Element(el) => { + let mut num_on_stack = 0; + for child in el.children.iter() { + num_on_stack += self.push_all_nodes(child); } - } - } - } + self.mutations.push_root(el.id.get().unwrap()); - /// schedules nodes for garbage collection and pushes "remove" to the mutation stack - /// remove can happen whenever - pub(crate) fn remove_nodes( - &mut self, - nodes: impl IntoIterator>, - gen_muts: bool, - ) { - // or cache the vec on the diff machine - for node in nodes { - log::debug!("removing {:?}", node); - match node { - VNode::Text(t) => { - // this check exists because our null node will be removed but does not have an ID - if let Some(id) = t.id.get() { - // self.scopes.collect_garbage(id); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - } - } - VNode::Placeholder(a) => { - let id = a.id.get().unwrap(); - // self.scopes.collect_garbage(id); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - } - VNode::Element(e) => { - let id = e.id.get().unwrap(); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - - self.remove_nodes(e.children, false); - - // self.scopes.collect_garbage(id); - } - - VNode::Fragment(f) => { - self.remove_nodes(f.children, gen_muts); - } - - VNode::Component(c) => { - let scope_id = c.scope.get().unwrap(); - let root = self.scopes.root_node(scope_id); - self.remove_nodes(Some(root), gen_muts); - - // we can only remove this node if the originator is actively - if self.stack.scope_stack.contains(&c.originator) { - self.scopes.try_remove(scope_id).unwrap(); - } - } + num_on_stack + 1 } } } diff --git a/packages/core/src/diff_async.rs b/packages/core/src/diff_async.rs deleted file mode 100644 index e5d63ffb5..000000000 --- a/packages/core/src/diff_async.rs +++ /dev/null @@ -1,1102 +0,0 @@ -use crate::innerlude::*; -use fxhash::{FxHashMap, FxHashSet}; -use smallvec::{smallvec, SmallVec}; - -pub(crate) struct AsyncDiffState<'bump> { - pub(crate) scopes: &'bump ScopeArena, - pub(crate) mutations: Mutations<'bump>, - pub(crate) force_diff: bool, - pub(crate) element_stack: SmallVec<[ElementId; 10]>, - pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, -} - -impl<'b> AsyncDiffState<'b> { - pub fn new(scopes: &'b ScopeArena) -> Self { - Self { - scopes, - mutations: Mutations::new(), - force_diff: false, - element_stack: smallvec![], - scope_stack: smallvec![], - } - } - - pub fn diff_scope(&mut self, scopeid: ScopeId) { - let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); - self.scope_stack.push(scopeid); - let scope = self.scopes.get_scope(scopeid).unwrap(); - self.element_stack.push(scope.container); - self.diff_node(old, new); - } - - pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { - use VNode::*; - match (old_node, new_node) { - // Check the most common cases first - // these are *actual* elements, not wrappers around lists - (Text(old), Text(new)) => { - if std::ptr::eq(old, new) { - log::trace!("skipping node diff - text are the sames"); - return; - } - - if let Some(root) = old.id.get() { - if old.text != new.text { - self.mutations.set_text(new.text, root.as_u64()); - } - self.scopes.update_node(new_node, root); - - new.id.set(Some(root)); - } - } - - (Placeholder(old), Placeholder(new)) => { - if std::ptr::eq(old, new) { - log::trace!("skipping node diff - placeholder are the sames"); - return; - } - - if let Some(root) = old.id.get() { - self.scopes.update_node(new_node, root); - new.id.set(Some(root)) - } - } - - (Element(old), Element(new)) => { - if std::ptr::eq(old, new) { - log::trace!("skipping node diff - element are the sames"); - return; - } - self.diff_element_nodes(old, new, old_node, new_node) - } - - // These two sets are pointers to nodes but are not actually nodes themselves - (Component(old), Component(new)) => { - if std::ptr::eq(old, new) { - log::trace!("skipping node diff - placeholder are the sames"); - return; - } - self.diff_component_nodes(old_node, new_node, *old, *new) - } - - (Fragment(old), Fragment(new)) => { - if std::ptr::eq(old, new) { - log::trace!("skipping node diff - fragment are the sames"); - return; - } - self.diff_fragment_nodes(old, new) - } - - // The normal pathway still works, but generates slightly weird instructions - // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove - (Placeholder(_), Fragment(_)) => { - log::debug!("replacing placeholder with fragment {:?}", new_node); - self.replace_node(old_node, new_node); - } - - // Anything else is just a basic replace and create - ( - Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), - Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), - ) => self.replace_node(old_node, new_node), - } - } - - pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize { - match node { - VNode::Text(vtext) => self.create_text_node(vtext, node), - VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node), - VNode::Element(element) => self.create_element_node(element, node), - VNode::Fragment(frag) => self.create_fragment_node(frag), - VNode::Component(component) => self.create_component_node(*component), - } - } - - fn create_text_node(&mut self, vtext: &'b VText<'b>, node: &'b VNode<'b>) -> usize { - let real_id = self.scopes.reserve_node(node); - self.mutations.create_text_node(vtext.text, real_id); - vtext.id.set(Some(real_id)); - - 1 - } - - fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize { - let real_id = self.scopes.reserve_node(node); - self.mutations.create_placeholder(real_id); - anchor.id.set(Some(real_id)); - - 1 - } - - fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize { - let VElement { - tag: tag_name, - listeners, - attributes, - children, - namespace, - id: dom_id, - parent: parent_id, - .. - } = element; - - // set the parent ID for event bubbling - // self.stack.instructions.push(DiffInstruction::PopElement); - - let parent = self.element_stack.last().unwrap(); - parent_id.set(Some(*parent)); - - // set the id of the element - let real_id = self.scopes.reserve_node(node); - self.element_stack.push(real_id); - dom_id.set(Some(real_id)); - - self.mutations.create_element(tag_name, *namespace, real_id); - - if let Some(cur_scope_id) = self.current_scope() { - for listener in *listeners { - listener.mounted_node.set(Some(real_id)); - self.mutations.new_event_listener(listener, cur_scope_id); - } - } else { - log::warn!("create element called with no scope on the stack - this is an error for a live dom"); - } - - for attr in *attributes { - self.mutations.set_attribute(attr, real_id.as_u64()); - } - - if !children.is_empty() { - self.create_and_append_children(children); - } - - 1 - } - - fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize { - self.create_children(frag.children) - } - - fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { - let parent_idx = self.current_scope().unwrap(); - - // the component might already exist - if it does, we need to reuse it - // this makes figure out when to drop the component more complicated - let new_idx = if let Some(idx) = vcomponent.scope.get() { - assert!(self.scopes.get_scope(idx).is_some()); - idx - } else { - // Insert a new scope into our component list - let props: Box = vcomponent.props.borrow_mut().take().unwrap(); - let props: Box = unsafe { std::mem::transmute(props) }; - let new_idx = self.scopes.new_with_key( - vcomponent.user_fc, - props, - Some(parent_idx), - self.element_stack.last().copied().unwrap(), - 0, - ); - - new_idx - }; - - log::info!( - "created component {:?} with parent {:?} and originator {:?}", - new_idx, - parent_idx, - vcomponent.originator - ); - - // Actually initialize the caller's slot with the right address - vcomponent.scope.set(Some(new_idx)); - - match vcomponent.can_memoize { - true => { - // todo: implement promotion logic. save us from boxing props that we don't need - } - false => { - // track this component internally so we know the right drop order - } - } - - // Run the scope for one iteration to initialize it - self.scopes.run_scope(new_idx); - - self.mutations.mark_dirty_scope(new_idx); - - // self.stack.create_component(new_idx, nextnode); - // Finally, insert this scope as a seen node. - - // Take the node that was just generated from running the component - let nextnode = self.scopes.fin_head(new_idx); - self.create_node(nextnode) - } - - fn diff_element_nodes( - &mut self, - old: &'b VElement<'b>, - new: &'b VElement<'b>, - old_node: &'b VNode<'b>, - new_node: &'b VNode<'b>, - ) { - let root = old.id.get().unwrap(); - - // If the element type is completely different, the element needs to be re-rendered completely - // This is an optimization React makes due to how users structure their code - // - // This case is rather rare (typically only in non-keyed lists) - if new.tag != old.tag || new.namespace != old.namespace { - self.replace_node(old_node, new_node); - return; - } - - self.scopes.update_node(new_node, root); - - new.id.set(Some(root)); - new.parent.set(old.parent.get()); - - // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the - // element to modify its attributes. - // it would result in fewer instructions if we just set the id directly. - // it would also clean up this code some, but that's not very important anyways - - // Diff Attributes - // - // It's extraordinarily rare to have the number/order of attributes change - // In these cases, we just completely erase the old set and make a new set - // - // TODO: take a more efficient path than this - if old.attributes.len() == new.attributes.len() { - for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) { - if old_attr.value != new_attr.value || new_attr.is_volatile { - self.mutations.set_attribute(new_attr, root.as_u64()); - } - } - } else { - for attribute in old.attributes { - self.mutations.remove_attribute(attribute, root.as_u64()); - } - for attribute in new.attributes { - self.mutations.set_attribute(attribute, root.as_u64()) - } - } - - // Diff listeners - // - // It's extraordinarily rare to have the number/order of listeners change - // In the cases where the listeners change, we completely wipe the data attributes and add new ones - // - // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) - // - // TODO: take a more efficient path than this - if let Some(cur_scope_id) = self.current_scope() { - if old.listeners.len() == new.listeners.len() { - for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { - if old_l.event != new_l.event { - self.mutations - .remove_event_listener(old_l.event, root.as_u64()); - self.mutations.new_event_listener(new_l, cur_scope_id); - } - new_l.mounted_node.set(old_l.mounted_node.get()); - } - } else { - for listener in old.listeners { - self.mutations - .remove_event_listener(listener.event, root.as_u64()); - } - for listener in new.listeners { - listener.mounted_node.set(Some(root)); - self.mutations.new_event_listener(listener, cur_scope_id); - } - } - } - - match (old.children.len(), new.children.len()) { - (0, 0) => {} - (0, _) => { - let created = self.create_children(new.children); - self.mutations.append_children(created as u32); - } - (_, _) => { - self.diff_children(old.children, new.children); - } - }; - } - - fn diff_component_nodes( - &mut self, - old_node: &'b VNode<'b>, - new_node: &'b VNode<'b>, - old: &'b VComponent<'b>, - new: &'b VComponent<'b>, - ) { - let scope_addr = old.scope.get().unwrap(); - log::trace!( - "diff_component_nodes. old: {:#?} new: {:#?}", - old_node, - new_node - ); - - if std::ptr::eq(old, new) { - log::trace!("skipping component diff - component is the sames"); - return; - } - - // Make sure we're dealing with the same component (by function pointer) - if old.user_fc == new.user_fc { - self.enter_scope(scope_addr); - - // Make sure the new component vnode is referencing the right scope id - new.scope.set(Some(scope_addr)); - - // make sure the component's caller function is up to date - let scope = self - .scopes - .get_scope(scope_addr) - .unwrap_or_else(|| panic!("could not find {:?}", scope_addr)); - - // take the new props out regardless - // when memoizing, push to the existing scope if memoization happens - let new_props = new.props.borrow_mut().take().unwrap(); - - let should_run = { - if old.can_memoize { - let props_are_the_same = unsafe { - scope - .props - .borrow() - .as_ref() - .unwrap() - .memoize(new_props.as_ref()) - }; - !props_are_the_same || self.force_diff - } else { - true - } - }; - - if should_run { - let _old_props = scope - .props - .replace(unsafe { std::mem::transmute(Some(new_props)) }); - - // this should auto drop the previous props - self.scopes.run_scope(scope_addr); - self.mutations.mark_dirty_scope(scope_addr); - - self.diff_node( - self.scopes.wip_head(scope_addr), - self.scopes.fin_head(scope_addr), - ); - } else { - log::trace!("memoized"); - // memoization has taken place - drop(new_props); - }; - - self.leave_scope(); - } else { - log::debug!("scope stack is {:#?}", self.scope_stack); - self.replace_node(old_node, new_node); - } - } - - fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { - // This is the case where options or direct vnodes might be used. - // In this case, it's faster to just skip ahead to their diff - if old.children.len() == 1 && new.children.len() == 1 { - self.diff_node(&old.children[0], &new.children[0]); - return; - } - - debug_assert!(!old.children.is_empty()); - debug_assert!(!new.children.is_empty()); - - self.diff_children(old.children, new.children); - } - - // ============================================= - // Utilities for creating new diff instructions - // ============================================= - - // Diff the given set of old and new children. - // - // The parent must be on top of the change list stack when this function is - // entered: - // - // [... parent] - // - // the change list stack is in the same state when this function returns. - // - // If old no anchors are provided, then it's assumed that we can freely append to the parent. - // - // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements. - // - // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only - // to an element, and appending makes sense. - fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { - // Remember, fragments can never be empty (they always have a single child) - match (old, new) { - ([], []) => {} - ([], _) => self.create_and_append_children(new), - (_, []) => self.remove_nodes(old, true), - _ => { - let new_is_keyed = new[0].key().is_some(); - let old_is_keyed = old[0].key().is_some(); - - debug_assert!( - new.iter().all(|n| n.key().is_some() == new_is_keyed), - "all siblings must be keyed or all siblings must be non-keyed" - ); - debug_assert!( - old.iter().all(|o| o.key().is_some() == old_is_keyed), - "all siblings must be keyed or all siblings must be non-keyed" - ); - - if new_is_keyed && old_is_keyed { - self.diff_keyed_children(old, new); - } else { - self.diff_non_keyed_children(old, new); - } - } - } - } - - // Diff children that are not keyed. - // - // The parent must be on the top of the change list stack when entering this - // function: - // - // [... parent] - // - // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { - // Handled these cases in `diff_children` before calling this function. - debug_assert!(!new.is_empty()); - debug_assert!(!old.is_empty()); - - use std::cmp::Ordering; - match old.len().cmp(&new.len()) { - Ordering::Greater => self.remove_nodes(&old[new.len()..], true), - Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), - Ordering::Equal => {} - } - - for (new, old) in new.iter().zip(old.iter()) { - self.diff_node(old, new); - } - } - - // Diffing "keyed" children. - // - // With keyed children, we care about whether we delete, move, or create nodes - // versus mutate existing nodes in place. Presumably there is some sort of CSS - // transition animation that makes the virtual DOM diffing algorithm - // observable. By specifying keys for nodes, we know which virtual DOM nodes - // must reuse (or not reuse) the same physical DOM nodes. - // - // This is loosely based on Inferno's keyed patching implementation. However, we - // have to modify the algorithm since we are compiling the diff down into change - // list instructions that will be executed later, rather than applying the - // changes to the DOM directly as we compare virtual DOMs. - // - // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 - // - // The stack is empty upon entry. - fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { - if cfg!(debug_assertions) { - let mut keys = fxhash::FxHashSet::default(); - let mut assert_unique_keys = |children: &'b [VNode<'b>]| { - keys.clear(); - for child in children { - let key = child.key(); - debug_assert!( - key.is_some(), - "if any sibling is keyed, all siblings must be keyed" - ); - keys.insert(key); - } - debug_assert_eq!( - children.len(), - keys.len(), - "keyed siblings must each have a unique key" - ); - }; - assert_unique_keys(old); - assert_unique_keys(new); - } - - // First up, we diff all the nodes with the same key at the beginning of the - // children. - // - // `shared_prefix_count` is the count of how many nodes at the start of - // `new` and `old` share the same keys. - let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) { - Some(count) => count, - None => return, - }; - - // Ok, we now hopefully have a smaller range of children in the middle - // within which to re-order nodes with the same keys, remove old nodes with - // now-unused keys, and create new nodes with fresh keys. - - let old_middle = &old[left_offset..(old.len() - right_offset)]; - let new_middle = &new[left_offset..(new.len() - right_offset)]; - - debug_assert!( - !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), - "keyed children must have the same number of children" - ); - - if new_middle.is_empty() { - // remove the old elements - self.remove_nodes(old_middle, true); - } else if old_middle.is_empty() { - // there were no old elements, so just create the new elements - // we need to find the right "foothold" though - we shouldn't use the "append" at all - if left_offset == 0 { - // insert at the beginning of the old list - let foothold = &old[old.len() - right_offset]; - self.create_and_insert_before(new_middle, foothold); - } else if right_offset == 0 { - // insert at the end the old list - let foothold = old.last().unwrap(); - self.create_and_insert_after(new_middle, foothold); - } else { - // inserting in the middle - let foothold = &old[left_offset - 1]; - self.create_and_insert_after(new_middle, foothold); - } - } else { - self.diff_keyed_middle(old_middle, new_middle); - } - } - - /// Diff both ends of the children that share keys. - /// - /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing. - /// - /// If there is no offset, then this function returns None and the diffing is complete. - fn diff_keyed_ends( - &mut self, - old: &'b [VNode<'b>], - new: &'b [VNode<'b>], - ) -> Option<(usize, usize)> { - let mut left_offset = 0; - - for (old, new) in old.iter().zip(new.iter()) { - // abort early if we finally run into nodes with different keys - if old.key() != new.key() { - break; - } - self.diff_node(old, new); - left_offset += 1; - } - - // If that was all of the old children, then create and append the remaining - // new children and we're finished. - if left_offset == old.len() { - self.create_and_insert_after(&new[left_offset..], old.last().unwrap()); - return None; - } - - // And if that was all of the new children, then remove all of the remaining - // old children and we're finished. - if left_offset == new.len() { - self.remove_nodes(&old[left_offset..], true); - return None; - } - - // if the shared prefix is less than either length, then we need to walk backwards - let mut right_offset = 0; - for (old, new) in old.iter().rev().zip(new.iter().rev()) { - // abort early if we finally run into nodes with different keys - if old.key() != new.key() { - break; - } - self.diff_node(old, new); - right_offset += 1; - } - - Some((left_offset, right_offset)) - } - - // The most-general, expensive code path for keyed children diffing. - // - // We find the longest subsequence within `old` of children that are relatively - // ordered the same way in `new` (via finding a longest-increasing-subsequence - // of the old child's index within `new`). The children that are elements of - // this subsequence will remain in place, minimizing the number of DOM moves we - // will have to do. - // - // Upon entry to this function, the change list stack must be empty. - // - // This function will load the appropriate nodes onto the stack and do diffing in place. - // - // Upon exit from this function, it will be restored to that same self. - fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { - /* - 1. Map the old keys into a numerical ordering based on indices. - 2. Create a map of old key to its index - 3. Map each new key to the old key, carrying over the old index. - - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3 - - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist - - now, we should have a list of integers that indicates where in the old list the new items map to. - - 4. Compute the LIS of this list - - this indicates the longest list of new children that won't need to be moved. - - 5. Identify which nodes need to be removed - 6. Identify which nodes will need to be diffed - - 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS. - - if the item already existed, just move it to the right place. - - 8. Finally, generate instructions to remove any old children. - 9. Generate instructions to finally diff children that are the same between both - */ - - // 0. Debug sanity checks - // Should have already diffed the shared-key prefixes and suffixes. - debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key())); - debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key())); - - // 1. Map the old keys into a numerical ordering based on indices. - // 2. Create a map of old key to its index - // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3). - let old_key_to_old_index = old - .iter() - .enumerate() - .map(|(i, o)| (o.key().unwrap(), i)) - .collect::>(); - - let mut shared_keys = FxHashSet::default(); - - // 3. Map each new key to the old key, carrying over the old index. - let new_index_to_old_index = new - .iter() - .map(|node| { - let key = node.key().unwrap(); - if let Some(&index) = old_key_to_old_index.get(&key) { - shared_keys.insert(key); - index - } else { - u32::MAX as usize - } - }) - .collect::>(); - - // If none of the old keys are reused by the new children, then we remove all the remaining old children and - // create the new children afresh. - if shared_keys.is_empty() { - if let Some(first_old) = old.get(0) { - self.remove_nodes(&old[1..], true); - let nodes_created = self.create_children(new); - self.replace_inner(first_old, nodes_created); - } else { - // I think this is wrong - why are we appending? - // only valid of the if there are no trailing elements - self.create_and_append_children(new); - } - return; - } - - // 4. Compute the LIS of this list - let mut lis_sequence = Vec::default(); - lis_sequence.reserve(new_index_to_old_index.len()); - - let mut predecessors = vec![0; new_index_to_old_index.len()]; - let mut starts = vec![0; new_index_to_old_index.len()]; - - longest_increasing_subsequence::lis_with( - &new_index_to_old_index, - &mut lis_sequence, - |a, b| a < b, - &mut predecessors, - &mut starts, - ); - - // the lis comes out backwards, I think. can't quite tell. - lis_sequence.sort_unstable(); - - // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) - if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { - lis_sequence.pop(); - } - - for idx in lis_sequence.iter() { - self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]); - } - - let mut nodes_created = 0; - - // add mount instruction for the first items not covered by the lis - let last = *lis_sequence.last().unwrap(); - if last < (new.len() - 1) { - for (idx, new_node) in new[(last + 1)..].iter().enumerate() { - let new_idx = idx + last + 1; - let old_index = new_index_to_old_index[new_idx]; - if old_index == u32::MAX as usize { - nodes_created += self.create_node(new_node); - } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_nodes(new_node); - } - } - - self.mutations.insert_after( - self.find_last_element(&new[last]).unwrap(), - nodes_created as u32, - ); - nodes_created = 0; - } - - // for each spacing, generate a mount instruction - let mut lis_iter = lis_sequence.iter().rev(); - let mut last = *lis_iter.next().unwrap(); - for next in lis_iter { - if last - next > 1 { - for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { - let new_idx = idx + next + 1; - - let old_index = new_index_to_old_index[new_idx]; - if old_index == u32::MAX as usize { - nodes_created += self.create_node(new_node); - } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_nodes(new_node); - } - } - - self.mutations.insert_before( - self.find_first_element(&new[last]).unwrap(), - nodes_created as u32, - ); - - nodes_created = 0; - } - last = *next; - } - - // add mount instruction for the last items not covered by the lis - let first_lis = *lis_sequence.first().unwrap(); - if first_lis > 0 { - for (idx, new_node) in new[..first_lis].iter().enumerate() { - let old_index = new_index_to_old_index[idx]; - if old_index == u32::MAX as usize { - nodes_created += self.create_node(new_node); - } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_nodes(new_node); - } - } - - self.mutations.insert_before( - self.find_first_element(&new[first_lis]).unwrap(), - nodes_created as u32, - ); - } - } - - fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { - // fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { - log::debug!("Replacing node {:?}", old); - let nodes_created = self.create_node(new); - self.replace_inner(old, nodes_created); - } - - fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) { - match old { - VNode::Element(el) => { - let id = old - .try_mounted_id() - .unwrap_or_else(|| panic!("broke on {:?}", old)); - - self.mutations.replace_with(id, nodes_created as u32); - self.remove_nodes(el.children, false); - self.scopes.collect_garbage(id); - } - - VNode::Text(_) | VNode::Placeholder(_) => { - let id = old - .try_mounted_id() - .unwrap_or_else(|| panic!("broke on {:?}", old)); - - self.mutations.replace_with(id, nodes_created as u32); - self.scopes.collect_garbage(id); - } - - VNode::Fragment(f) => { - self.replace_inner(&f.children[0], nodes_created); - self.remove_nodes(f.children.iter().skip(1), true); - } - - VNode::Component(c) => { - let node = self.scopes.fin_head(c.scope.get().unwrap()); - self.replace_node(node, node); - - let scope_id = c.scope.get().unwrap(); - - // we can only remove components if they are actively being diffed - if self.scope_stack.contains(&c.originator) { - self.scopes.try_remove(scope_id).unwrap(); - } - } - } - } - - pub fn remove_nodes(&mut self, nodes: impl IntoIterator>, gen_muts: bool) { - for node in nodes { - match node { - VNode::Text(t) => { - // this check exists because our null node will be removed but does not have an ID - if let Some(id) = t.id.get() { - self.scopes.collect_garbage(id); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - } - } - VNode::Placeholder(a) => { - let id = a.id.get().unwrap(); - self.scopes.collect_garbage(id); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - } - VNode::Element(e) => { - let id = e.id.get().unwrap(); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - - self.scopes.collect_garbage(id); - - self.remove_nodes(e.children, false); - } - - VNode::Fragment(f) => { - self.remove_nodes(f.children, gen_muts); - } - - VNode::Component(c) => { - let scope_id = c.scope.get().unwrap(); - let root = self.scopes.root_node(scope_id); - self.remove_nodes([root], gen_muts); - - // we can only remove this node if the originator is actively - if self.scope_stack.contains(&c.originator) { - self.scopes.try_remove(scope_id).unwrap(); - } - } - } - } - } - - fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize { - let mut created = 0; - for node in nodes { - created += self.create_node(node); - } - created - } - - fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) { - let created = self.create_children(nodes); - self.mutations.append_children(created as u32); - } - - fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) { - let created = self.create_children(nodes); - let last = self.find_last_element(after).unwrap(); - self.mutations.insert_after(last, created as u32); - } - - fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) { - let created = self.create_children(nodes); - let first = self.find_first_element(before).unwrap(); - self.mutations.insert_before(first, created as u32); - } - - fn current_scope(&self) -> Option { - self.scope_stack.last().copied() - } - - fn enter_scope(&mut self, scope: ScopeId) { - self.scope_stack.push(scope); - } - - fn leave_scope(&mut self) { - self.scope_stack.pop(); - } - - fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option { - let mut search_node = Some(vnode); - - loop { - match &search_node.take().unwrap() { - VNode::Text(t) => break t.id.get(), - VNode::Element(t) => break t.id.get(), - VNode::Placeholder(t) => break t.id.get(), - VNode::Fragment(frag) => { - search_node = frag.children.last(); - } - VNode::Component(el) => { - let scope_id = el.scope.get().unwrap(); - search_node = Some(self.scopes.root_node(scope_id)); - } - } - } - } - - fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option { - let mut search_node = Some(vnode); - - loop { - match &search_node.take().unwrap() { - // the ones that have a direct id - VNode::Fragment(frag) => { - search_node = Some(&frag.children[0]); - } - VNode::Component(el) => { - let scope_id = el.scope.get().unwrap(); - search_node = Some(self.scopes.root_node(scope_id)); - } - VNode::Text(t) => break t.id.get(), - VNode::Element(t) => break t.id.get(), - VNode::Placeholder(t) => break t.id.get(), - } - } - } - - // fn replace_node(&mut self, old: &'b VNode<'b>, nodes_created: usize) { - // log::debug!("Replacing node {:?}", old); - // match old { - // VNode::Element(el) => { - // let id = old - // .try_mounted_id() - // .unwrap_or_else(|| panic!("broke on {:?}", old)); - - // self.mutations.replace_with(id, nodes_created as u32); - // self.remove_nodes(el.children, false); - // } - - // VNode::Text(_) | VNode::Placeholder(_) => { - // let id = old - // .try_mounted_id() - // .unwrap_or_else(|| panic!("broke on {:?}", old)); - - // self.mutations.replace_with(id, nodes_created as u32); - // } - - // VNode::Fragment(f) => { - // self.replace_node(&f.children[0], nodes_created); - // self.remove_nodes(f.children.iter().skip(1), true); - // } - - // VNode::Component(c) => { - // let node = self.scopes.fin_head(c.scope.get().unwrap()); - // self.replace_node(node, nodes_created); - - // let scope_id = c.scope.get().unwrap(); - - // // we can only remove components if they are actively being diffed - // if self.stack.scope_stack.contains(&c.originator) { - // self.scopes.try_remove(scope_id).unwrap(); - // } - // } - // } - // } - - // /// schedules nodes for garbage collection and pushes "remove" to the mutation stack - // /// remove can happen whenever - // pub(crate) fn remove_nodes( - // &mut self, - // nodes: impl IntoIterator>, - // gen_muts: bool, - // ) { - // // or cache the vec on the diff machine - // for node in nodes { - // log::debug!("removing {:?}", node); - // match node { - // VNode::Text(t) => { - // // this check exists because our null node will be removed but does not have an ID - // if let Some(id) = t.id.get() { - // // self.scopes.collect_garbage(id); - - // if gen_muts { - // self.mutations.remove(id.as_u64()); - // } - // } - // } - // VNode::Placeholder(a) => { - // let id = a.id.get().unwrap(); - // // self.scopes.collect_garbage(id); - - // if gen_muts { - // self.mutations.remove(id.as_u64()); - // } - // } - // VNode::Element(e) => { - // let id = e.id.get().unwrap(); - - // if gen_muts { - // self.mutations.remove(id.as_u64()); - // } - - // self.remove_nodes(e.children, false); - - // // self.scopes.collect_garbage(id); - // } - - // VNode::Fragment(f) => { - // self.remove_nodes(f.children, gen_muts); - // } - - // VNode::Component(c) => { - // let scope_id = c.scope.get().unwrap(); - // let root = self.scopes.root_node(scope_id); - // self.remove_nodes(Some(root), gen_muts); - - // // we can only remove this node if the originator is actively - // if self.stack.scope_stack.contains(&c.originator) { - // self.scopes.try_remove(scope_id).unwrap(); - // } - // } - // } - // } - // } - - // recursively push all the nodes of a tree onto the stack and return how many are there - fn push_all_nodes(&mut self, node: &'b VNode<'b>) -> usize { - match node { - VNode::Text(_) | VNode::Placeholder(_) => { - self.mutations.push_root(node.mounted_id()); - 1 - } - - VNode::Fragment(_) | VNode::Component(_) => { - // - let mut added = 0; - for child in node.children() { - added += self.push_all_nodes(child); - } - added - } - - VNode::Element(el) => { - let mut num_on_stack = 0; - for child in el.children.iter() { - num_on_stack += self.push_all_nodes(child); - } - self.mutations.push_root(el.id.get().unwrap()); - - num_on_stack + 1 - } - } - } -} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 48a8a3505..dba4effbb 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,8 +1,7 @@ #![allow(non_snake_case)] #![doc = include_str!("../README.md")] -// pub(crate) mod diff; -pub(crate) mod diff_async; +pub(crate) mod diff; pub(crate) mod events; pub(crate) mod lazynodes; pub(crate) mod mutations; @@ -13,8 +12,7 @@ pub(crate) mod util; pub(crate) mod virtual_dom; pub(crate) mod innerlude { - pub(crate) use crate::diff_async::*; - // pub(crate) use crate::diff::*; + pub(crate) use crate::diff::*; pub use crate::events::*; pub use crate::lazynodes::*; pub use crate::mutations::*; diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 542afc673..0ebbea9b9 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -255,7 +255,7 @@ impl ScopeArena { let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") }; // if cfg!(debug_assertions) { - log::debug!("running scope {:?} symbol: {:?}", id, scope.fnptr); + // log::debug!("running scope {:?} symbol: {:?}", id, scope.fnptr); // todo: resolve frames properly backtrace::resolve(scope.fnptr, |symbol| { @@ -733,7 +733,6 @@ impl ScopeState { while let Some(parent_ptr) = search_parent { // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; - log::trace!("Searching parent scope {:?}", parent.scope_id()); if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { return Some(shared.clone().downcast::().unwrap()); } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index d879e0340..032aa6198 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -2,7 +2,7 @@ //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. -use crate::diff_async::AsyncDiffState as DiffState; +use crate::diff::AsyncDiffState as DiffState; use crate::innerlude::*; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{future::poll_fn, StreamExt}; diff --git a/packages/core/tests/earlyabort.rs b/packages/core/tests/earlyabort.rs index 7dc2ae4d4..3999a7402 100644 --- a/packages/core/tests/earlyabort.rs +++ b/packages/core/tests/earlyabort.rs @@ -42,14 +42,8 @@ fn test_early_abort() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 1, - }, - CreateTextNode { - text: "Hello, world!", - root: 2, - }, + CreateElement { tag: "div", root: 1 }, + CreateTextNode { text: "Hello, world!", root: 2 }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, ] @@ -65,14 +59,8 @@ fn test_early_abort() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 2, - }, - CreateTextNode { - text: "Hello, world!", - root: 4, - }, + CreateElement { tag: "div", root: 1 }, // keys get reused + CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused AppendChildren { many: 1 }, ReplaceWith { root: 3, m: 1 }, ] diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 6c0df4e1e..f977ba96e 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -27,12 +27,7 @@ fn manual_diffing() { }; let value = Arc::new(Mutex::new("Hello")); - let mut dom = VirtualDom::new_with_props( - App, - AppProps { - value: value.clone(), - }, - ); + let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() }); let _ = dom.rebuild(); @@ -45,7 +40,7 @@ fn manual_diffing() { #[test] fn events_generate() { - static App: Component = |cx| { + fn app(cx: Scope) -> Element { let count = cx.use_hook(|_| 0); let inner = match *count { @@ -66,7 +61,7 @@ fn events_generate() { cx.render(inner) }; - let mut dom = VirtualDom::new(App); + let mut dom = VirtualDom::new(app); let mut channel = dom.get_scheduler_channel(); assert!(dom.has_work()); @@ -74,28 +69,12 @@ fn events_generate() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 1, - }, - NewEventListener { - event_name: "click", - scope: ScopeId(0), - root: 1, - }, - CreateElement { - tag: "div", - root: 2, - }, - CreateTextNode { - text: "nested", - root: 3, - }, + CreateElement { tag: "div", root: 1 }, + NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 }, + CreateElement { tag: "div", root: 2 }, + CreateTextNode { text: "nested", root: 3 }, AppendChildren { many: 1 }, - CreateTextNode { - text: "Click me!", - root: 4, - }, + CreateTextNode { text: "Click me!", root: 4 }, AppendChildren { many: 2 }, AppendChildren { many: 1 }, ] @@ -104,7 +83,7 @@ fn events_generate() { #[test] fn components_generate() { - static App: Component = |cx| { + fn app(cx: Scope) -> Element { let render_phase = cx.use_hook(|_| 0); *render_phase += 1; @@ -121,106 +100,84 @@ fn components_generate() { }) }; - static Child: Component = |cx| { + fn Child(cx: Scope) -> Element { + log::debug!("Running child"); cx.render(rsx! { h1 {} }) - }; + } - let mut dom = VirtualDom::new(App); + let mut dom = VirtualDom::new(app); let edits = dom.rebuild(); assert_eq!( edits.edits, [ - CreateTextNode { - text: "Text0", - root: 1, - }, + CreateTextNode { text: "Text0", root: 1 }, AppendChildren { many: 1 }, ] ); - let edits = dom.hard_diff(ScopeId(0)); assert_eq!( - edits.edits, + dom.hard_diff(ScopeId(0)).edits, [ - CreateElement { - tag: "div", - root: 2, - }, + CreateElement { tag: "div", root: 2 }, ReplaceWith { root: 1, m: 1 }, ] ); - let edits = dom.hard_diff(ScopeId(0)); assert_eq!( - edits.edits, + dom.hard_diff(ScopeId(0)).edits, [ - CreateTextNode { - text: "Text2", - root: 3, - }, + CreateTextNode { text: "Text2", root: 1 }, ReplaceWith { root: 2, m: 1 }, ] ); - let edits = dom.hard_diff(ScopeId(0)); + // child {} assert_eq!( - edits.edits, + dom.hard_diff(ScopeId(0)).edits, [ - CreateElement { tag: "h1", root: 4 }, - ReplaceWith { root: 3, m: 1 }, + CreateElement { tag: "h1", root: 2 }, + ReplaceWith { root: 1, m: 1 }, ] ); - let edits = dom.hard_diff(ScopeId(0)); + // placeholder assert_eq!( - edits.edits, - [CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },] + dom.hard_diff(ScopeId(0)).edits, + [CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },] ); - let edits = dom.hard_diff(ScopeId(0)); assert_eq!( - edits.edits, + dom.hard_diff(ScopeId(0)).edits, [ - CreateTextNode { - text: "text 3", - root: 6, - }, - ReplaceWith { root: 5, m: 1 }, + CreateTextNode { text: "text 3", root: 2 }, + ReplaceWith { root: 1, m: 1 }, ] ); - let edits = dom.hard_diff(ScopeId(0)); assert_eq!( - edits.edits, + dom.hard_diff(ScopeId(0)).edits, [ - CreateTextNode { - text: "text 0", - root: 7, - }, - CreateTextNode { - text: "text 1", - root: 8, - }, - ReplaceWith { root: 6, m: 2 }, + CreateTextNode { text: "text 0", root: 1 }, + CreateTextNode { text: "text 1", root: 3 }, + ReplaceWith { root: 2, m: 2 }, ] ); - let edits = dom.hard_diff(ScopeId(0)); assert_eq!( - edits.edits, + dom.hard_diff(ScopeId(0)).edits, [ - CreateElement { tag: "h1", root: 9 }, - ReplaceWith { root: 7, m: 1 }, - Remove { root: 8 }, + CreateElement { tag: "h1", root: 2 }, + ReplaceWith { root: 1, m: 1 }, + Remove { root: 3 }, ] ); } #[test] fn component_swap() { - static App: Component = |cx| { + fn app(cx: Scope) -> Element { let render_phase = cx.use_hook(|_| 0); *render_phase += 1; @@ -296,7 +253,7 @@ fn component_swap() { }) }; - let mut dom = VirtualDom::new(App); + let mut dom = VirtualDom::new(app); let edits = dom.rebuild(); dbg!(&edits); diff --git a/packages/core/tests/passthru.rs b/packages/core/tests/passthru.rs new file mode 100644 index 000000000..e00aa545c --- /dev/null +++ b/packages/core/tests/passthru.rs @@ -0,0 +1,108 @@ +#![allow(unused, non_upper_case_globals)] + +//! Diffing Tests +//! +//! These tests only verify that the diffing algorithm works properly for single components. +//! +//! It does not validated that component lifecycles work properly. This is done in another test file. + +use dioxus::{prelude::*, DomEdit, ScopeId}; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; + +mod test_logging; + +fn new_dom() -> VirtualDom { + const IS_LOGGING_ENABLED: bool = false; + test_logging::set_up_logging(IS_LOGGING_ENABLED); + VirtualDom::new(|cx| rsx!(cx, "hi")) +} + +use DomEdit::*; + +/// Should push the text node onto the stack and modify it +#[test] +fn nested_passthru_creates() { + fn app(cx: Scope) -> Element { + cx.render(rsx! { + Child { + Child { + Child { + div { + "hi" + } + } + } + } + }) + }; + + #[inline_props] + fn Child<'a>(cx: Scope, children: Element<'a>) -> Element { + cx.render(rsx! { + children + }) + }; + + let mut dom = VirtualDom::new(app); + let mut channel = dom.get_scheduler_channel(); + assert!(dom.has_work()); + + let edits = dom.rebuild(); + assert_eq!( + edits.edits, + [ + CreateElement { tag: "div", root: 1 }, + CreateTextNode { text: "hi", root: 2 }, + AppendChildren { many: 1 }, + AppendChildren { many: 1 }, + ] + ) +} + +/// Should push the text node onto the stack and modify it +#[test] +fn nested_passthru_creates_add() { + fn app(cx: Scope) -> Element { + cx.render(rsx! { + Child { + "1" + Child { + "2" + Child { + "3" + div { + "hi" + } + } + } + } + }) + }; + + #[inline_props] + fn Child<'a>(cx: Scope, children: Element<'a>) -> Element { + cx.render(rsx! { + children + }) + }; + + let mut dom = VirtualDom::new(app); + let mut channel = dom.get_scheduler_channel(); + assert!(dom.has_work()); + + let edits = dom.rebuild(); + assert_eq!( + edits.edits, + [ + CreateTextNode { text: "1", root: 1 }, + CreateTextNode { text: "2", root: 2 }, + CreateTextNode { text: "3", root: 3 }, + CreateElement { tag: "div", root: 4 }, + CreateTextNode { text: "hi", root: 5 }, + AppendChildren { many: 1 }, + AppendChildren { many: 4 }, + ] + ) +} From a0ffe66a371744f5a38697f06e6fee89f2db2809 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 31 Jan 2022 09:47:09 +0800 Subject: [PATCH 043/103] feat: add `borderless` example --- .gitignore | 1 + examples/borderless.rs | 47 +++++++++++++++++++------ packages/desktop/src/desktop_context.rs | 5 ++- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 9534dc207..9df7e9f93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target +/dist Cargo.lock .DS_Store diff --git a/examples/borderless.rs b/examples/borderless.rs index 18bbbb80b..7f85ff22d 100644 --- a/examples/borderless.rs +++ b/examples/borderless.rs @@ -1,21 +1,48 @@ -use dioxus::prelude::*; +use dioxus::{events::onmousedown, prelude::*}; use dioxus_desktop::desktop_context::DesktopContext; fn main() { dioxus::desktop::launch_cfg(app, |cfg| { - cfg.with_window(|w| { - w.with_title("BorderLess Demo") - .with_decorations(false) - }) + cfg.with_window(|w| w.with_title("BorderLess Demo").with_decorations(false)) }); } -fn app (cx: Scope) -> Element { +fn app(cx: Scope) -> Element { let desktop = cx.consume_context::().unwrap(); + + let drag = desktop.clone(); + let close = desktop.clone(); + let min = desktop.clone(); + cx.render(rsx!( - div { - style: "background-color: black; height: 20px; width: 100%", - onmousedown: move |_| desktop.drag_window(), + link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } + header { + class: "text-gray-400 bg-gray-900 body-font", + onmousedown: move |_| drag.drag_window(), + div { + class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center", + a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0", + span { class: "ml-3 text-xl", "Dioxus"} + } + nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center", + // a { class: "mr-5 hover:text-white", "First Link"} + // a { class: "mr-5 hover:text-white", "Second Link"} + // a { class: "mr-5 hover:text-white", "Third Link"} + // a { class: "mr-5 hover:text-white", "Fourth Link"} + } + button { + class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", + onmousedown: |evt| evt.cancel_bubble(), + onclick: move |_| min.minimize(true), + "Minimize" + } + button { + class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", + onmousedown: |evt| evt.cancel_bubble(), + onclick: move |_| close.close(), + "Close" + } + } } )) -} \ No newline at end of file +} diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 577e33c54..456dba1c1 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -20,7 +20,7 @@ pub struct DesktopContext { impl DesktopContext { - pub fn new(proxy: ProxyType) -> Self { + pub(crate) fn new(proxy: ProxyType) -> Self { Self { proxy } } @@ -36,14 +36,17 @@ impl DesktopContext { let _ = self.proxy.send_event(UserWindowEvent::DragWindow); } + /// set window minimize state pub fn minimize(&self, minimized: bool) { let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized)); } + /// set window maximize state pub fn maximize(&self, maximized: bool) { let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized)); } + /// close window pub fn close(&self) { let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); } From e4eb982a367ed588b370e4a28e8cf2a94d0f4b24 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 31 Jan 2022 10:11:58 +0800 Subject: [PATCH 044/103] fix: format code --- examples/borderless.rs | 14 ++++---------- packages/desktop/src/desktop_context.rs | 14 ++++++-------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/examples/borderless.rs b/examples/borderless.rs index 7f85ff22d..e6cd4eda1 100644 --- a/examples/borderless.rs +++ b/examples/borderless.rs @@ -1,4 +1,4 @@ -use dioxus::{events::onmousedown, prelude::*}; +use dioxus::prelude::*; use dioxus_desktop::desktop_context::DesktopContext; fn main() { @@ -12,11 +12,10 @@ fn app(cx: Scope) -> Element { let drag = desktop.clone(); let close = desktop.clone(); - let min = desktop.clone(); cx.render(rsx!( link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } - header { + header { class: "text-gray-400 bg-gray-900 body-font", onmousedown: move |_| drag.drag_window(), div { @@ -24,16 +23,11 @@ fn app(cx: Scope) -> Element { a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0", span { class: "ml-3 text-xl", "Dioxus"} } - nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center", - // a { class: "mr-5 hover:text-white", "First Link"} - // a { class: "mr-5 hover:text-white", "Second Link"} - // a { class: "mr-5 hover:text-white", "Third Link"} - // a { class: "mr-5 hover:text-white", "Fourth Link"} - } + nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", onmousedown: |evt| evt.cancel_bubble(), - onclick: move |_| min.minimize(true), + onclick: move |_| desktop.minimize(true), "Minimize" } button { diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 456dba1c1..0a1fa2b02 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -5,11 +5,11 @@ use crate::UserWindowEvent; type ProxyType = EventLoopProxy; /// Desktop-Window handle api context -/// +/// /// you can use this context control some window event -/// +/// /// you can use `cx.consume_context::` to get this context -/// +/// /// ```rust /// let desktop = cx.consume_context::().unwrap(); /// ``` @@ -19,15 +19,14 @@ pub struct DesktopContext { } impl DesktopContext { - pub(crate) fn new(proxy: ProxyType) -> Self { Self { proxy } } /// trigger the drag-window event - /// + /// /// Moves the window with the left mouse button until the button is released. - /// + /// /// you need use it in `onmousedown` event: /// ```rust /// onmousedown: move |_| { desktop.drag_window(); } @@ -50,5 +49,4 @@ impl DesktopContext { pub fn close(&self) { let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); } - -} \ No newline at end of file +} From cbd471fa46d80fb3ea4d5e484be4bf62a31f770f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 01:24:11 -0500 Subject: [PATCH 045/103] debugging: add some more debug tooling --- packages/core/src/diff.rs | 24 ++++++++++++++++++++---- packages/core/src/nodes.rs | 16 +++++++--------- packages/core/src/scopes.rs | 8 ++++---- packages/core/src/virtual_dom.rs | 13 +++++++++++++ 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index d790292c8..6f7f12fa4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -406,10 +406,14 @@ impl<'b> AsyncDiffState<'b> { fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { // This is the case where options or direct vnodes might be used. // In this case, it's faster to just skip ahead to their diff - if old.children.len() == 1 && new.children.len() == 1 { - self.diff_node(&old.children[0], &new.children[0]); - return; - } + // if old.children.len() == 1 && new.children.len() == 1 { + // if std::ptr::eq(old, new) { + // log::debug!("skipping fragment diff - fragment is the same"); + // return; + // } + // self.diff_node(&old.children[0], &new.children[0]); + // return; + // } debug_assert!(!old.children.is_empty()); debug_assert!(!new.children.is_empty()); @@ -437,6 +441,11 @@ impl<'b> AsyncDiffState<'b> { // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only // to an element, and appending makes sense. fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + if std::ptr::eq(old, new) { + log::debug!("skipping fragment diff - fragment is the same"); + return; + } + // Remember, fragments can never be empty (they always have a single child) match (old, new) { ([], []) => {} @@ -484,7 +493,13 @@ impl<'b> AsyncDiffState<'b> { Ordering::Equal => {} } + // panic!( + // "diff_children: new_is_keyed: {:#?}, old_is_keyed: {:#?}. stack: {:#?}, oldptr: {:#?}, newptr: {:#?}", + // old, new, self.scope_stack, old as *const _, new as *const _ + // ); + for (new, old) in new.iter().zip(old.iter()) { + log::debug!("diffing nonkeyed {:#?} {:#?}", old, new); self.diff_node(old, new); } } @@ -893,6 +908,7 @@ impl<'b> AsyncDiffState<'b> { // we can only remove this node if the originator is actively if self.scope_stack.contains(&c.originator) { + log::debug!("I am allowed to remove component because scope stack contains originator. Scope stack: {:#?} {:#?} {:#?}", self.scope_stack, c.originator, c.scope); self.scopes.try_remove(scope_id).unwrap(); } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 74234a84c..2f1453e1d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -182,15 +182,13 @@ impl Debug for VNode<'_> { VNode::Fragment(frag) => { write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) } - VNode::Component(comp) => { - s.debug_struct("VNode::VComponent") - .field("fnptr", &comp.user_fc) - .field("key", &comp.key) - .field("scope", &comp.scope) - .field("originator", &comp.originator) - .finish() - //write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc) - } + VNode::Component(comp) => s + .debug_struct("VNode::VComponent") + .field("fnptr", &comp.user_fc) + .field("key", &comp.key) + .field("scope", &comp.scope) + .field("originator", &comp.originator) + .finish(), } } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 0ebbea9b9..93a0c94a7 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -98,7 +98,8 @@ impl ScopeArena { let height = parent_scope .map(|id| self.get_scope(id).map(|scope| scope.height)) .flatten() - .unwrap_or_default(); + .unwrap_or_default() + + 1; let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten(); @@ -203,9 +204,8 @@ impl ScopeArena { } pub fn collect_garbage(&self, id: ElementId) { - // println!("collecting garbage for {:?}", id); - // log::debug!("collecting garbage for {:?}", id); - self.nodes.borrow_mut().remove(id.0); + let node = self.nodes.borrow_mut().remove(id.0); + log::debug!("collecting garbage for {:?}, {:?}", id, unsafe { &*node }); } /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 032aa6198..613ea9292 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -462,14 +462,27 @@ impl VirtualDom { self.dirty_scopes .retain(|id| scopes.get_scope(*id).is_some()); + log::debug!("dirty_scopes: {:?}", self.dirty_scopes); + + for id in &self.dirty_scopes { + let scope = scopes.get_scope(*id).unwrap(); + + log::debug!("dirty scope: {:?} with height {:?}", id, scope.height); + } + self.dirty_scopes.sort_by(|a, b| { let h1 = scopes.get_scope(*a).unwrap().height; let h2 = scopes.get_scope(*b).unwrap().height; h1.cmp(&h2).reverse() }); + log::debug!("dirty_scopes: {:?}", self.dirty_scopes); + if let Some(scopeid) = self.dirty_scopes.pop() { + log::debug!("diffing scope {:?}", scopeid); if !ran_scopes.contains(&scopeid) { + log::debug!("running scope scope {:?}", scopeid); + ran_scopes.insert(scopeid); self.scopes.run_scope(scopeid); From 11f6b938891ab67a05de6926373fbf331577775d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 02:33:16 -0500 Subject: [PATCH 046/103] fix: remove nodes is in a happier state --- packages/core-macro/src/rsx/component.rs | 3 ++ packages/core/src/diff.rs | 37 ++++++++++++++++-------- packages/core/src/mutations.rs | 6 ++-- packages/core/src/nodes.rs | 4 +++ packages/core/src/scopes.rs | 24 ++++++++++----- packages/core/src/virtual_dom.rs | 14 ++------- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 5257022a4..4e9d449cf 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -135,11 +135,14 @@ impl ToTokens for Component { None => quote! { None }, }; + let fn_name = self.name.segments.last().unwrap().ident.to_string(); + tokens.append_all(quote! { __cx.component( #name, #builder, #key_token, + #fn_name ) }) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 6f7f12fa4..db898f194 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -26,7 +26,10 @@ impl<'b> AsyncDiffState<'b> { self.scope_stack.push(scopeid); let scope = self.scopes.get_scope(scopeid).unwrap(); self.element_stack.push(scope.container); + self.diff_node(old, new); + + self.mutations.mark_dirty_scope(scopeid); } pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { @@ -87,13 +90,6 @@ impl<'b> AsyncDiffState<'b> { self.diff_fragment_nodes(old, new) } - // The normal pathway still works, but generates slightly weird instructions - // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove - (Placeholder(_), Fragment(_)) => { - log::debug!("replacing placeholder with fragment {:?}", new_node); - self.replace_node(old_node, new_node); - } - // Anything else is just a basic replace and create ( Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), @@ -201,7 +197,8 @@ impl<'b> AsyncDiffState<'b> { }; log::info!( - "created component {:?} with parent {:?} and originator {:?}", + "created component \"{}\", id: {:?} parent {:?} orig: {:?}", + vcomponent.fn_name, new_idx, parent_idx, vcomponent.originator @@ -442,7 +439,7 @@ impl<'b> AsyncDiffState<'b> { // to an element, and appending makes sense. fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { if std::ptr::eq(old, new) { - log::debug!("skipping fragment diff - fragment is the same"); + log::debug!("skipping fragment diff - fragment is the same {:?}", old); return; } @@ -499,7 +496,7 @@ impl<'b> AsyncDiffState<'b> { // ); for (new, old) in new.iter().zip(old.iter()) { - log::debug!("diffing nonkeyed {:#?} {:#?}", old, new); + // log::debug!("diffing nonkeyed {:#?} {:#?}", old, new); self.diff_node(old, new); } } @@ -849,10 +846,15 @@ impl<'b> AsyncDiffState<'b> { } VNode::Component(c) => { - let node = self.scopes.fin_head(c.scope.get().unwrap()); + log::info!("Replacing component {:?}", old); + let scope_id = c.scope.get().unwrap(); + let node = self.scopes.fin_head(scope_id); + + self.enter_scope(scope_id); + self.replace_inner(node, nodes_created); - let scope_id = c.scope.get().unwrap(); + log::info!("Replacing component x2 {:?}", old); // we can only remove components if they are actively being diffed if self.scope_stack.contains(&c.originator) { @@ -860,6 +862,7 @@ impl<'b> AsyncDiffState<'b> { self.scopes.try_remove(scope_id).unwrap(); } + self.leave_scope(); } } } @@ -902,15 +905,25 @@ impl<'b> AsyncDiffState<'b> { } VNode::Component(c) => { + self.enter_scope(c.scope.get().unwrap()); + let scope_id = c.scope.get().unwrap(); let root = self.scopes.root_node(scope_id); self.remove_nodes([root], gen_muts); + log::info!( + "trying to remove scope {:?}, stack is {:#?}, originator is {:?}", + scope_id, + self.scope_stack, + c.originator + ); + // we can only remove this node if the originator is actively if self.scope_stack.contains(&c.originator) { log::debug!("I am allowed to remove component because scope stack contains originator. Scope stack: {:#?} {:#?} {:#?}", self.scope_stack, c.originator, c.scope); self.scopes.try_remove(scope_id).unwrap(); } + self.leave_scope(); } } } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index bd94d0b71..5af727154 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -17,7 +17,7 @@ use std::{any::Any, fmt::Debug}; /// Mutations are the only link between the RealDOM and the VirtualDOM. pub struct Mutations<'a> { pub edits: Vec>, - pub dirty_scopes: FxHashSet, + pub diffed_scopes: FxHashSet, pub refs: Vec>, } @@ -118,7 +118,7 @@ impl<'a> Mutations<'a> { Self { edits: Vec::new(), refs: Vec::new(), - dirty_scopes: Default::default(), + diffed_scopes: Default::default(), } } @@ -223,7 +223,7 @@ impl<'a> Mutations<'a> { } pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) { - self.dirty_scopes.insert(scope); + self.diffed_scopes.insert(scope); } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 2f1453e1d..e95f3f825 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -184,6 +184,7 @@ impl Debug for VNode<'_> { } VNode::Component(comp) => s .debug_struct("VNode::VComponent") + .field("name", &comp.fn_name) .field("fnptr", &comp.user_fc) .field("key", &comp.key) .field("scope", &comp.scope) @@ -390,6 +391,7 @@ pub struct VComponent<'src> { pub scope: Cell>, pub can_memoize: bool, pub user_fc: FcSlot, + pub fn_name: &'static str, pub props: RefCell>>, } @@ -540,6 +542,7 @@ impl<'a> NodeFactory<'a> { component: fn(Scope<'a, P>) -> Element, props: P, key: Option, + fn_name: &'static str, ) -> VNode<'a> where P: Properties + 'a, @@ -550,6 +553,7 @@ impl<'a> NodeFactory<'a> { can_memoize: P::IS_STATIC, user_fc: component as *mut std::os::raw::c_void, originator: self.scope.scope_id(), + fn_name, props: RefCell::new(Some(Box::new(VComponentProps { // local_props: RefCell::new(Some(props)), // heap_props: RefCell::new(None), diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 93a0c94a7..351bcf4f7 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -113,13 +113,22 @@ impl ScopeArena { if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { // reuse the old scope let scope = unsafe { &mut *old_scope }; - scope.props.get_mut().replace(vcomp); + + scope.container = container; + scope.our_arena_idx = new_scope_id; scope.parent_scope = parent_scope; scope.height = height; - scope.subtree.set(subtree); - scope.our_arena_idx = new_scope_id; - scope.container = container; scope.fnptr = fc_ptr; + scope.props.get_mut().replace(vcomp); + scope.subtree.set(subtree); + scope.frames[0].reset(); + scope.frames[1].reset(); + scope.shared_contexts.get_mut().clear(); + scope.items.get_mut().listeners.clear(); + scope.items.get_mut().borrowed_props.clear(); + scope.hook_idx.set(0); + scope.hook_vals.get_mut().clear(); + let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope); debug_assert!(any_item.is_none()); } else { @@ -177,7 +186,7 @@ impl ScopeArena { let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() }; scope.reset(); - self.free_scopes.borrow_mut().push(scope); + // self.free_scopes.borrow_mut().push(scope); Some(()) } @@ -204,8 +213,9 @@ impl ScopeArena { } pub fn collect_garbage(&self, id: ElementId) { - let node = self.nodes.borrow_mut().remove(id.0); - log::debug!("collecting garbage for {:?}, {:?}", id, unsafe { &*node }); + let node = self.nodes.borrow_mut().get(id.0).unwrap().clone(); + // let node = self.nodes.borrow_mut().remove(id.0); + // log::debug!("collecting garbage for {:?}, {:?}", id, unsafe { &*node }); } /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 613ea9292..9c4cd4018 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -462,14 +462,6 @@ impl VirtualDom { self.dirty_scopes .retain(|id| scopes.get_scope(*id).is_some()); - log::debug!("dirty_scopes: {:?}", self.dirty_scopes); - - for id in &self.dirty_scopes { - let scope = scopes.get_scope(*id).unwrap(); - - log::debug!("dirty scope: {:?} with height {:?}", id, scope.height); - } - self.dirty_scopes.sort_by(|a, b| { let h1 = scopes.get_scope(*a).unwrap().height; let h2 = scopes.get_scope(*b).unwrap().height; @@ -479,10 +471,7 @@ impl VirtualDom { log::debug!("dirty_scopes: {:?}", self.dirty_scopes); if let Some(scopeid) = self.dirty_scopes.pop() { - log::debug!("diffing scope {:?}", scopeid); if !ran_scopes.contains(&scopeid) { - log::debug!("running scope scope {:?}", scopeid); - ran_scopes.insert(scopeid); self.scopes.run_scope(scopeid); @@ -491,7 +480,8 @@ impl VirtualDom { let AsyncDiffState { mutations, .. } = diff_state; - for scope in &mutations.dirty_scopes { + log::debug!("succesffuly resolved scopes {:?}", mutations.diffed_scopes); + for scope in &mutations.diffed_scopes { self.dirty_scopes.remove(scope); } From 5bffbba682c634b2882d43e574d7415981f0d45e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 02:40:12 -0500 Subject: [PATCH 047/103] fmt: apply cargofmt with new rules for lit structs --- packages/core/src/events.rs | 4 +- packages/core/src/lazynodes.rs | 27 +----- packages/core/src/mutations.rs | 34 ++----- packages/core/src/nodes.rs | 6 +- packages/core/src/scopes.rs | 10 +- packages/core/src/util.rs | 5 +- packages/core/tests/create_dom.rs | 140 ++++++---------------------- packages/core/tests/diffing.rs | 1 - packages/core/tests/miri_stress.rs | 7 +- packages/core/tests/sharedstate.rs | 5 +- packages/core/tests/vdom_rebuild.rs | 10 +- 11 files changed, 54 insertions(+), 195 deletions(-) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 8386cedd8..597a12992 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -12,9 +12,7 @@ pub(crate) struct BubbleState { impl BubbleState { pub fn new() -> Self { - Self { - canceled: Cell::new(false), - } + Self { canceled: Cell::new(false) } } } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 1340c916c..0fbed45e1 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -55,9 +55,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { fac.map(inner) }; - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } + Self { inner: StackNodeStorage::Heap(Box::new(val)) } } pub fn new(_val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self { @@ -71,9 +69,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { // miri does not know how to work with mucking directly into bytes if cfg!(miri) { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } + Self { inner: StackNodeStorage::Heap(Box::new(val)) } } else { unsafe { LazyNodes::new_inner(val) } } @@ -117,9 +113,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { let max_size = mem::size_of::(); if stored_size > max_size { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } + Self { inner: StackNodeStorage::Heap(Box::new(val)) } } else { let mut buf: StackHeapSize = StackHeapSize::default(); @@ -143,13 +137,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { std::mem::forget(val); - Self { - inner: StackNodeStorage::Stack(LazyStack { - _align: [], - buf, - dropped: false, - }), - } + Self { inner: StackNodeStorage::Stack(LazyStack { _align: [], buf, dropped: false }) } } } @@ -298,12 +286,7 @@ mod tests { } let inner = Rc::new(0); - let mut dom = VirtualDom::new_with_props( - app, - AppProps { - inner: inner.clone(), - }, - ); + let mut dom = VirtualDom::new_with_props(app, AppProps { inner: inner.clone() }); dom.rebuild(); drop(dom); diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 5af727154..6a0785e23 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -115,11 +115,7 @@ use DomEdit::*; impl<'a> Mutations<'a> { pub(crate) fn new() -> Self { - Self { - edits: Vec::new(), - refs: Vec::new(), - diffed_scopes: Default::default(), - } + Self { edits: Vec::new(), refs: Vec::new(), diffed_scopes: Default::default() } } // Navigation @@ -178,19 +174,12 @@ impl<'a> Mutations<'a> { // events pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { - let Listener { - event, - mounted_node, - .. - } = listener; + let Listener { event, mounted_node, .. } = listener; let element_id = mounted_node.get().unwrap().as_u64(); - self.edits.push(NewEventListener { - scope, - event_name: event, - root: element_id, - }); + self.edits + .push(NewEventListener { scope, event_name: event, root: element_id }); } pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: u64) { self.edits.push(RemoveEventListener { event, root }); @@ -202,19 +191,10 @@ impl<'a> Mutations<'a> { } pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) { - let Attribute { - name, - value, - namespace, - .. - } = attribute; + let Attribute { name, value, namespace, .. } = attribute; - self.edits.push(SetAttribute { - field: name, - value, - ns: *namespace, - root, - }); + self.edits + .push(SetAttribute { field: name, value, ns: *namespace, root }); } pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e95f3f825..264fb56bb 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{Element, FcSlot, Properties, Scope, ScopeId, ScopeState}, + innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, lazynodes::LazyNodes, AnyEvent, Component, }; @@ -390,7 +390,7 @@ pub struct VComponent<'src> { pub originator: ScopeId, pub scope: Cell>, pub can_memoize: bool, - pub user_fc: FcSlot, + pub user_fc: ComponentPtr, pub fn_name: &'static str, pub props: RefCell>>, } @@ -551,7 +551,7 @@ impl<'a> NodeFactory<'a> { key: key.map(|f| self.raw_text(f).0), scope: Default::default(), can_memoize: P::IS_STATIC, - user_fc: component as *mut std::os::raw::c_void, + user_fc: component as ComponentPtr, originator: self.scope.scope_id(), fn_name, props: RefCell::new(Some(Box::new(VComponentProps { diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 351bcf4f7..6b65c9197 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -14,8 +14,8 @@ use std::{ }; /// for traceability, we use the raw fn pointer to identify the function -/// we can use this with the traceback crate to resolve funciton names -pub(crate) type FcSlot = *mut std::os::raw::c_void; +/// we also get the component name, but that's not necessarily unique in the app +pub(crate) type ComponentPtr = *mut std::os::raw::c_void; pub(crate) struct Heuristic { hook_arena_size: usize, @@ -30,7 +30,7 @@ pub(crate) struct ScopeArena { pub scope_gen: Cell, pub bump: Bump, pub scopes: RefCell>, - pub heuristics: RefCell>, + pub heuristics: RefCell>, pub free_scopes: RefCell>, pub nodes: RefCell>>, pub tasks: Rc, @@ -84,7 +84,7 @@ impl ScopeArena { pub(crate) fn new_with_key( &self, - fc_ptr: FcSlot, + fc_ptr: ComponentPtr, vcomp: Box, parent_scope: Option, container: ElementId, @@ -469,7 +469,7 @@ pub struct ScopeState { pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, - pub(crate) fnptr: FcSlot, + pub(crate) fnptr: ComponentPtr, // todo: subtrees pub(crate) is_subtree_root: Cell, diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index b9640792d..24caab579 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -10,10 +10,7 @@ pub struct ElementIdIterator<'a> { impl<'a> ElementIdIterator<'a> { pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self { - Self { - vdom, - stack: smallvec::smallvec![(0, node)], - } + Self { vdom, stack: smallvec::smallvec![(0, node)] } } } diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 044be289c..0cdc37c6d 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -36,18 +36,9 @@ fn test_original_diff() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, - text: "Hello, world!" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateTextNode { root: 3, text: "Hello, world!" }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, @@ -81,34 +72,13 @@ fn create() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, - text: "Hello, world!" - }, - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, - CreateTextNode { - root: 6, - text: "hello" - }, - CreateTextNode { - root: 7, - text: "world" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateTextNode { root: 3, text: "Hello, world!" }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, + CreateTextNode { root: 6, text: "hello" }, + CreateTextNode { root: 7, text: "world" }, AppendChildren { many: 2 }, AppendChildren { many: 1 }, AppendChildren { many: 2 }, @@ -135,32 +105,14 @@ fn create_list() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "hello" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "div" - }, - CreateTextNode { - root: 4, - text: "hello" - }, + CreateElement { root: 3, tag: "div" }, + CreateTextNode { root: 4, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { - root: 5, - tag: "div" - }, - CreateTextNode { - root: 6, - text: "hello" - }, + CreateElement { root: 5, tag: "div" }, + CreateTextNode { root: 6, text: "hello" }, AppendChildren { many: 1 }, AppendChildren { many: 3 }, ] @@ -185,22 +137,10 @@ fn create_simple() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateElement { - root: 3, - tag: "div" - }, - CreateElement { - root: 4, - tag: "div" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateElement { root: 3, tag: "div" }, + CreateElement { root: 4, tag: "div" }, AppendChildren { many: 4 }, ] ); @@ -235,36 +175,18 @@ fn create_components() { mutations.edits, [ CreateElement { root: 1, tag: "h1" }, - CreateElement { - root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, - text: "abc1" - }, + CreateElement { root: 2, tag: "div" }, + CreateTextNode { root: 3, text: "abc1" }, AppendChildren { many: 1 }, CreateElement { root: 4, tag: "p" }, CreateElement { root: 5, tag: "h1" }, - CreateElement { - root: 6, - tag: "div" - }, - CreateTextNode { - root: 7, - text: "abc2" - }, + CreateElement { root: 6, tag: "div" }, + CreateTextNode { root: 7, text: "abc2" }, AppendChildren { many: 1 }, CreateElement { root: 8, tag: "p" }, CreateElement { root: 9, tag: "h1" }, - CreateElement { - root: 10, - tag: "div" - }, - CreateTextNode { - root: 11, - text: "abc3" - }, + CreateElement { root: 10, tag: "div" }, + CreateTextNode { root: 11, text: "abc3" }, AppendChildren { many: 1 }, CreateElement { root: 12, tag: "p" }, AppendChildren { many: 9 }, @@ -285,14 +207,8 @@ fn anchors() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "hello" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "hello" }, AppendChildren { many: 1 }, CreatePlaceholder { root: 3 }, AppendChildren { many: 2 }, diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index c407359c9..6bc9b5596 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -580,7 +580,6 @@ fn keyed_diffing_additions_and_moves_in_middle() { }) }); - // LIS: 4, 5, 6 let (_, change) = dom.diff_lazynodes(left, right); log::debug!("{:#?}", change); diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 0f4f64546..8ce030147 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -152,12 +152,7 @@ fn free_works_on_root_props() { } } - let mut dom = new_dom( - app, - Custom { - val: String::from("asd"), - }, - ); + let mut dom = new_dom(app, Custom { val: String::from("asd") }); dom.rebuild(); } diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 41b178793..70e823569 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -29,10 +29,7 @@ fn shared_state_test() { assert_eq!( edits, [ - CreateTextNode { - root: 1, - text: "Hello, world!" - }, + CreateTextNode { root: 1, text: "Hello, world!" }, AppendChildren { many: 1 }, ] ); diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 5d1dbcdf8..83b4ff35f 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -68,15 +68,9 @@ fn conditional_rendering() { mutations.edits, [ CreateElement { root: 1, tag: "h1" }, - CreateTextNode { - root: 2, - text: "hello" - }, + CreateTextNode { root: 2, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "span" - }, + CreateElement { root: 3, tag: "span" }, CreateTextNode { root: 4, text: "a" }, AppendChildren { many: 1 }, CreatePlaceholder { root: 5 }, From 00aa0e5e86d298c8498b15c742d0bbe3981dd649 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 02:44:16 -0500 Subject: [PATCH 048/103] fmt: apply formatting just to tests --- packages/core/src/events.rs | 4 +- packages/core/src/lazynodes.rs | 27 +++++++++--- packages/core/src/mutations.rs | 34 ++++++++++++--- packages/core/src/nodes.rs | 58 +++++++++++++++++-------- packages/core/src/scopes.rs | 29 ++++++++++--- packages/core/src/util.rs | 5 ++- packages/core/{ => tests}/.rustfmt.toml | 0 7 files changed, 119 insertions(+), 38 deletions(-) rename packages/core/{ => tests}/.rustfmt.toml (100%) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 597a12992..8386cedd8 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -12,7 +12,9 @@ pub(crate) struct BubbleState { impl BubbleState { pub fn new() -> Self { - Self { canceled: Cell::new(false) } + Self { + canceled: Cell::new(false), + } } } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 0fbed45e1..1340c916c 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -55,7 +55,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> { fac.map(inner) }; - Self { inner: StackNodeStorage::Heap(Box::new(val)) } + Self { + inner: StackNodeStorage::Heap(Box::new(val)), + } } pub fn new(_val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self { @@ -69,7 +71,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> { // miri does not know how to work with mucking directly into bytes if cfg!(miri) { - Self { inner: StackNodeStorage::Heap(Box::new(val)) } + Self { + inner: StackNodeStorage::Heap(Box::new(val)), + } } else { unsafe { LazyNodes::new_inner(val) } } @@ -113,7 +117,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> { let max_size = mem::size_of::(); if stored_size > max_size { - Self { inner: StackNodeStorage::Heap(Box::new(val)) } + Self { + inner: StackNodeStorage::Heap(Box::new(val)), + } } else { let mut buf: StackHeapSize = StackHeapSize::default(); @@ -137,7 +143,13 @@ impl<'a, 'b> LazyNodes<'a, 'b> { std::mem::forget(val); - Self { inner: StackNodeStorage::Stack(LazyStack { _align: [], buf, dropped: false }) } + Self { + inner: StackNodeStorage::Stack(LazyStack { + _align: [], + buf, + dropped: false, + }), + } } } @@ -286,7 +298,12 @@ mod tests { } let inner = Rc::new(0); - let mut dom = VirtualDom::new_with_props(app, AppProps { inner: inner.clone() }); + let mut dom = VirtualDom::new_with_props( + app, + AppProps { + inner: inner.clone(), + }, + ); dom.rebuild(); drop(dom); diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 6a0785e23..5af727154 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -115,7 +115,11 @@ use DomEdit::*; impl<'a> Mutations<'a> { pub(crate) fn new() -> Self { - Self { edits: Vec::new(), refs: Vec::new(), diffed_scopes: Default::default() } + Self { + edits: Vec::new(), + refs: Vec::new(), + diffed_scopes: Default::default(), + } } // Navigation @@ -174,12 +178,19 @@ impl<'a> Mutations<'a> { // events pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { - let Listener { event, mounted_node, .. } = listener; + let Listener { + event, + mounted_node, + .. + } = listener; let element_id = mounted_node.get().unwrap().as_u64(); - self.edits - .push(NewEventListener { scope, event_name: event, root: element_id }); + self.edits.push(NewEventListener { + scope, + event_name: event, + root: element_id, + }); } pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: u64) { self.edits.push(RemoveEventListener { event, root }); @@ -191,10 +202,19 @@ impl<'a> Mutations<'a> { } pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) { - let Attribute { name, value, namespace, .. } = attribute; + let Attribute { + name, + value, + namespace, + .. + } = attribute; - self.edits - .push(SetAttribute { field: name, value, ns: *namespace, root }); + self.edits.push(SetAttribute { + field: name, + value, + ns: *namespace, + root, + }); } pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 264fb56bb..b849819f0 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -365,7 +365,9 @@ pub struct EventHandler<'bump, T = ()> { impl<'a, T> Default for EventHandler<'a, T> { fn default() -> Self { - Self { callback: RefCell::new(None) } + Self { + callback: RefCell::new(None), + } } } @@ -440,7 +442,10 @@ pub struct NodeFactory<'a> { impl<'a> NodeFactory<'a> { pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> { - NodeFactory { scope, bump: &scope.wip_frame().bump } + NodeFactory { + scope, + bump: &scope.wip_frame().bump, + } } #[inline] @@ -450,10 +455,11 @@ impl<'a> NodeFactory<'a> { /// Directly pass in text blocks without the need to use the format_args macro. pub fn static_text(&self, text: &'static str) -> VNode<'a> { - VNode::Text( - self.bump - .alloc(VText { id: empty_cell(), text, is_static: true }), - ) + VNode::Text(self.bump.alloc(VText { + id: empty_cell(), + text, + is_static: true, + })) } /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static @@ -476,7 +482,11 @@ impl<'a> NodeFactory<'a> { pub fn text(&self, args: Arguments) -> VNode<'a> { let (text, is_static) = self.raw_text(args); - VNode::Text(self.bump.alloc(VText { text, is_static, id: empty_cell() })) + VNode::Text(self.bump.alloc(VText { + text, + is_static, + id: empty_cell(), + })) } pub fn element( @@ -534,7 +544,13 @@ impl<'a> NodeFactory<'a> { is_volatile: bool, ) -> Attribute<'a> { let (value, is_static) = self.raw_text(val); - Attribute { name, value, is_static, namespace, is_volatile } + Attribute { + name, + value, + is_static, + namespace, + is_volatile, + } } pub fn component

( @@ -578,7 +594,11 @@ impl<'a> NodeFactory<'a> { } pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> { - Listener { event, mounted_node: Cell::new(None), callback } + Listener { + event, + mounted_node: Cell::new(None), + callback, + } } pub fn fragment_root<'b, 'c>( @@ -594,10 +614,10 @@ impl<'a> NodeFactory<'a> { if nodes.is_empty() { VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() })) } else { - VNode::Fragment( - self.bump - .alloc(VFragment { children: nodes.into_bump_slice(), key: None }), - ) + VNode::Fragment(self.bump.alloc(VFragment { + children: nodes.into_bump_slice(), + key: None, + })) } } @@ -633,7 +653,10 @@ impl<'a> NodeFactory<'a> { ); } - VNode::Fragment(self.bump.alloc(VFragment { children, key: None })) + VNode::Fragment(self.bump.alloc(VFragment { + children, + key: None, + })) } } @@ -656,9 +679,10 @@ impl<'a> NodeFactory<'a> { } else { let children = nodes.into_bump_slice(); - Some(VNode::Fragment( - self.bump.alloc(VFragment { children, key: None }), - )) + Some(VNode::Fragment(self.bump.alloc(VFragment { + children, + key: None, + }))) } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 6b65c9197..c6c0cb846 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -315,9 +315,11 @@ impl ScopeArena { frame.node.set(unsafe { extend_vnode(node) }); } else { let frame = scope.wip_frame(); - let node = frame.bump.alloc(VNode::Placeholder( - frame.bump.alloc(VPlaceholder { id: Default::default() }), - )); + let node = frame + .bump + .alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder { + id: Default::default(), + }))); frame.node.set(unsafe { extend_vnode(node) }); } @@ -427,7 +429,10 @@ pub struct Scope<'a, P = ()> { impl

Copy for Scope<'_, P> {} impl

Clone for Scope<'_, P> { fn clone(&self) -> Self { - Self { scope: self.scope, props: self.props } + Self { + scope: self.scope, + props: self.props, + } } } @@ -789,7 +794,10 @@ impl ScopeState { /// } ///``` pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option> { - Some(rsx.call(NodeFactory { scope: self, bump: &self.wip_frame().bump })) + Some(rsx.call(NodeFactory { + scope: self, + bump: &self.wip_frame().bump, + })) } /// Store a value between renders @@ -896,7 +904,10 @@ impl ScopeState { self.shared_contexts.get_mut().clear(); // next: reset the node data - let SelfReferentialItems { borrowed_props, listeners } = self.items.get_mut(); + let SelfReferentialItems { + borrowed_props, + listeners, + } = self.items.get_mut(); borrowed_props.clear(); listeners.clear(); self.frames[0].reset(); @@ -954,7 +965,11 @@ pub(crate) struct TaskQueue { pub(crate) type InnerTask = Pin>>; impl TaskQueue { fn new(sender: UnboundedSender) -> Rc { - Rc::new(Self { tasks: RefCell::new(FxHashMap::default()), gen: Cell::new(0), sender }) + Rc::new(Self { + tasks: RefCell::new(FxHashMap::default()), + gen: Cell::new(0), + sender, + }) } fn push_fut(&self, task: impl Future + 'static) -> TaskId { let pinned = Box::pin(task); diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index 24caab579..b9640792d 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -10,7 +10,10 @@ pub struct ElementIdIterator<'a> { impl<'a> ElementIdIterator<'a> { pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self { - Self { vdom, stack: smallvec::smallvec![(0, node)] } + Self { + vdom, + stack: smallvec::smallvec![(0, node)], + } } } diff --git a/packages/core/.rustfmt.toml b/packages/core/tests/.rustfmt.toml similarity index 100% rename from packages/core/.rustfmt.toml rename to packages/core/tests/.rustfmt.toml From 4ae11b5756340b086d8df5137cbf27a84b8f83b9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 02:46:26 -0500 Subject: [PATCH 049/103] fmt: make tests easier to visually parse with fmt rules --- packages/core/tests/.rustfmt.toml | 1 + packages/core/tests/create_dom.rs | 140 ++++------------- packages/core/tests/diffing.rs | 233 ++++++---------------------- packages/core/tests/earlyabort.rs | 20 +-- packages/core/tests/lifecycle.rs | 63 ++------ packages/core/tests/miri_stress.rs | 7 +- packages/core/tests/sharedstate.rs | 5 +- packages/core/tests/vdom_rebuild.rs | 10 +- 8 files changed, 95 insertions(+), 384 deletions(-) create mode 100644 packages/core/tests/.rustfmt.toml diff --git a/packages/core/tests/.rustfmt.toml b/packages/core/tests/.rustfmt.toml new file mode 100644 index 000000000..70a9483a8 --- /dev/null +++ b/packages/core/tests/.rustfmt.toml @@ -0,0 +1 @@ +struct_lit_width = 80 diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 044be289c..0cdc37c6d 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -36,18 +36,9 @@ fn test_original_diff() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, - text: "Hello, world!" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateTextNode { root: 3, text: "Hello, world!" }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, @@ -81,34 +72,13 @@ fn create() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, - text: "Hello, world!" - }, - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, - CreateTextNode { - root: 6, - text: "hello" - }, - CreateTextNode { - root: 7, - text: "world" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateTextNode { root: 3, text: "Hello, world!" }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, + CreateTextNode { root: 6, text: "hello" }, + CreateTextNode { root: 7, text: "world" }, AppendChildren { many: 2 }, AppendChildren { many: 1 }, AppendChildren { many: 2 }, @@ -135,32 +105,14 @@ fn create_list() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "hello" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "div" - }, - CreateTextNode { - root: 4, - text: "hello" - }, + CreateElement { root: 3, tag: "div" }, + CreateTextNode { root: 4, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { - root: 5, - tag: "div" - }, - CreateTextNode { - root: 6, - text: "hello" - }, + CreateElement { root: 5, tag: "div" }, + CreateTextNode { root: 6, text: "hello" }, AppendChildren { many: 1 }, AppendChildren { many: 3 }, ] @@ -185,22 +137,10 @@ fn create_simple() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateElement { - root: 3, - tag: "div" - }, - CreateElement { - root: 4, - tag: "div" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateElement { root: 3, tag: "div" }, + CreateElement { root: 4, tag: "div" }, AppendChildren { many: 4 }, ] ); @@ -235,36 +175,18 @@ fn create_components() { mutations.edits, [ CreateElement { root: 1, tag: "h1" }, - CreateElement { - root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, - text: "abc1" - }, + CreateElement { root: 2, tag: "div" }, + CreateTextNode { root: 3, text: "abc1" }, AppendChildren { many: 1 }, CreateElement { root: 4, tag: "p" }, CreateElement { root: 5, tag: "h1" }, - CreateElement { - root: 6, - tag: "div" - }, - CreateTextNode { - root: 7, - text: "abc2" - }, + CreateElement { root: 6, tag: "div" }, + CreateTextNode { root: 7, text: "abc2" }, AppendChildren { many: 1 }, CreateElement { root: 8, tag: "p" }, CreateElement { root: 9, tag: "h1" }, - CreateElement { - root: 10, - tag: "div" - }, - CreateTextNode { - root: 11, - text: "abc3" - }, + CreateElement { root: 10, tag: "div" }, + CreateTextNode { root: 11, text: "abc3" }, AppendChildren { many: 1 }, CreateElement { root: 12, tag: "p" }, AppendChildren { many: 9 }, @@ -285,14 +207,8 @@ fn anchors() { assert_eq!( mutations.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "hello" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "hello" }, AppendChildren { many: 1 }, CreatePlaceholder { root: 3 }, AppendChildren { many: 2 }, diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 5b98ea80e..66012cf99 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -31,26 +31,14 @@ fn html_and_rsx_generate_the_same_output() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "Hello world" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "Hello world" }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, ] ); - assert_eq!( - change.edits, - [SetText { - text: "Goodbye world", - root: 2 - },] - ); + assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]); } /// Should result in 3 elements on the stack @@ -67,32 +55,14 @@ fn fragments_create_properly() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - root: 2, - text: "Hello a" - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { root: 2, text: "Hello a" }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "div" - }, - CreateTextNode { - root: 4, - text: "Hello b" - }, + CreateElement { root: 3, tag: "div" }, + CreateTextNode { root: 4, text: "Hello b" }, AppendChildren { many: 1 }, - CreateElement { - root: 5, - tag: "div" - }, - CreateTextNode { - root: 6, - text: "Hello c" - }, + CreateElement { root: 5, tag: "div" }, + CreateTextNode { root: 6, text: "Hello c" }, AppendChildren { many: 1 }, AppendChildren { many: 3 }, ] @@ -116,10 +86,7 @@ fn empty_fragments_create_anchors() { assert_eq!( change.edits, [ - CreateElement { - root: 2, - tag: "div" - }, + CreateElement { root: 2, tag: "div" }, ReplaceWith { m: 1, root: 1 } ] ); @@ -141,26 +108,11 @@ fn empty_fragments_create_many_anchors() { assert_eq!( change.edits, [ - CreateElement { - root: 2, - tag: "div" - }, - CreateElement { - root: 3, - tag: "div" - }, - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 2, tag: "div" }, + CreateElement { root: 3, tag: "div" }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, + CreateElement { root: 6, tag: "div" }, ReplaceWith { m: 5, root: 1 } ] ); @@ -188,32 +140,14 @@ fn empty_fragments_create_anchors_with_many_children() { assert_eq!( change.edits, [ - CreateElement { - tag: "div", - root: 2, - }, - CreateTextNode { - text: "hello: 0", - root: 3 - }, + CreateElement { tag: "div", root: 2 }, + CreateTextNode { text: "hello: 0", root: 3 }, AppendChildren { many: 1 }, - CreateElement { - tag: "div", - root: 4, - }, - CreateTextNode { - text: "hello: 1", - root: 5 - }, + CreateElement { tag: "div", root: 4 }, + CreateTextNode { text: "hello: 1", root: 5 }, AppendChildren { many: 1 }, - CreateElement { - tag: "div", - root: 6, - }, - CreateTextNode { - text: "hello: 2", - root: 7 - }, + CreateElement { tag: "div", root: 6 }, + CreateTextNode { text: "hello: 2", root: 7 }, AppendChildren { many: 1 }, ReplaceWith { root: 1, m: 3 } ] @@ -236,23 +170,11 @@ fn many_items_become_fragment() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateTextNode { - text: "hello", - root: 2 - }, + CreateElement { root: 1, tag: "div" }, + CreateTextNode { text: "hello", root: 2 }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "div" - }, - CreateTextNode { - text: "hello", - root: 4 - }, + CreateElement { root: 3, tag: "div" }, + CreateTextNode { text: "hello", root: 4 }, AppendChildren { many: 1 }, AppendChildren { many: 2 }, ] @@ -339,26 +261,11 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, - CreateElement { - root: 3, - tag: "div" - }, - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, + CreateElement { root: 3, tag: "div" }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, CreateElement { root: 6, tag: "p" }, AppendChildren { many: 6 }, ] @@ -395,14 +302,8 @@ fn two_fragments_with_same_elements_are_differet() { assert_eq!( create.edits, [ - CreateElement { - root: 1, - tag: "div" - }, - CreateElement { - root: 2, - tag: "div" - }, + CreateElement { root: 1, tag: "div" }, + CreateElement { root: 2, tag: "div" }, CreateElement { root: 3, tag: "p" }, AppendChildren { many: 3 }, ] @@ -410,18 +311,9 @@ fn two_fragments_with_same_elements_are_differet() { assert_eq!( change.edits, [ - CreateElement { - root: 4, - tag: "div" - }, - CreateElement { - root: 5, - tag: "div" - }, - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 4, tag: "div" }, + CreateElement { root: 5, tag: "div" }, + CreateElement { root: 6, tag: "div" }, InsertAfter { root: 2, n: 3 }, ] ); @@ -628,14 +520,8 @@ fn keyed_diffing_additions() { assert_eq!( change.edits, [ - CreateElement { - root: 6, - tag: "div" - }, - CreateElement { - root: 7, - tag: "div" - }, + CreateElement { root: 6, tag: "div" }, + CreateElement { root: 7, tag: "div" }, InsertAfter { n: 2, root: 5 } ] ); @@ -663,14 +549,8 @@ fn keyed_diffing_additions_and_moves_on_ends() { change.edits, [ // create 11, 12 - CreateElement { - tag: "div", - root: 5 - }, - CreateElement { - tag: "div", - root: 6 - }, + CreateElement { tag: "div", root: 5 }, + CreateElement { tag: "div", root: 6 }, InsertAfter { root: 3, n: 2 }, // move 7 to the front PushRoot { root: 4 }, @@ -702,24 +582,12 @@ fn keyed_diffing_additions_and_moves_in_middle() { change.edits, [ // create 13, 17 - CreateElement { - tag: "div", - root: 5 - }, - CreateElement { - tag: "div", - root: 6 - }, + CreateElement { tag: "div", root: 5 }, + CreateElement { tag: "div", root: 6 }, InsertBefore { root: 2, n: 2 }, // create 11, 12 - CreateElement { - tag: "div", - root: 7 - }, - CreateElement { - tag: "div", - root: 8 - }, + CreateElement { tag: "div", root: 7 }, + CreateElement { tag: "div", root: 8 }, InsertBefore { root: 3, n: 2 }, // move 7 PushRoot { root: 4 }, @@ -756,16 +624,10 @@ fn controlled_keyed_diffing_out_of_order() { // remove 7 // create 9 and insert before 6 - CreateElement { - root: 5, - tag: "div" - }, + CreateElement { root: 5, tag: "div" }, InsertBefore { n: 1, root: 3 }, // create 0 and insert before 5 - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 6, tag: "div" }, InsertBefore { n: 1, root: 2 }, ] ); @@ -792,10 +654,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() { assert_eq!( changes.edits, [ - CreateElement { - root: 6, - tag: "div" - }, + CreateElement { root: 6, tag: "div" }, InsertBefore { n: 1, root: 3 }, PushRoot { root: 4 }, InsertBefore { n: 1, root: 1 }, diff --git a/packages/core/tests/earlyabort.rs b/packages/core/tests/earlyabort.rs index 7dc2ae4d4..ae26c97b6 100644 --- a/packages/core/tests/earlyabort.rs +++ b/packages/core/tests/earlyabort.rs @@ -42,14 +42,8 @@ fn test_early_abort() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 1, - }, - CreateTextNode { - text: "Hello, world!", - root: 2, - }, + CreateElement { tag: "div", root: 1 }, + CreateTextNode { text: "Hello, world!", root: 2 }, AppendChildren { many: 1 }, AppendChildren { many: 1 }, ] @@ -65,14 +59,8 @@ fn test_early_abort() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 2, - }, - CreateTextNode { - text: "Hello, world!", - root: 4, - }, + CreateElement { tag: "div", root: 2 }, + CreateTextNode { text: "Hello, world!", root: 4 }, AppendChildren { many: 1 }, ReplaceWith { root: 3, m: 1 }, ] diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 6c0df4e1e..8b7d7bb4a 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -27,12 +27,7 @@ fn manual_diffing() { }; let value = Arc::new(Mutex::new("Hello")); - let mut dom = VirtualDom::new_with_props( - App, - AppProps { - value: value.clone(), - }, - ); + let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() }); let _ = dom.rebuild(); @@ -74,28 +69,12 @@ fn events_generate() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 1, - }, - NewEventListener { - event_name: "click", - scope: ScopeId(0), - root: 1, - }, - CreateElement { - tag: "div", - root: 2, - }, - CreateTextNode { - text: "nested", - root: 3, - }, + CreateElement { tag: "div", root: 1 }, + NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 }, + CreateElement { tag: "div", root: 2 }, + CreateTextNode { text: "nested", root: 3 }, AppendChildren { many: 1 }, - CreateTextNode { - text: "Click me!", - root: 4, - }, + CreateTextNode { text: "Click me!", root: 4 }, AppendChildren { many: 2 }, AppendChildren { many: 1 }, ] @@ -132,10 +111,7 @@ fn components_generate() { assert_eq!( edits.edits, [ - CreateTextNode { - text: "Text0", - root: 1, - }, + CreateTextNode { text: "Text0", root: 1 }, AppendChildren { many: 1 }, ] ); @@ -144,10 +120,7 @@ fn components_generate() { assert_eq!( edits.edits, [ - CreateElement { - tag: "div", - root: 2, - }, + CreateElement { tag: "div", root: 2 }, ReplaceWith { root: 1, m: 1 }, ] ); @@ -156,10 +129,7 @@ fn components_generate() { assert_eq!( edits.edits, [ - CreateTextNode { - text: "Text2", - root: 3, - }, + CreateTextNode { text: "Text2", root: 3 }, ReplaceWith { root: 2, m: 1 }, ] ); @@ -183,10 +153,7 @@ fn components_generate() { assert_eq!( edits.edits, [ - CreateTextNode { - text: "text 3", - root: 6, - }, + CreateTextNode { text: "text 3", root: 6 }, ReplaceWith { root: 5, m: 1 }, ] ); @@ -195,14 +162,8 @@ fn components_generate() { assert_eq!( edits.edits, [ - CreateTextNode { - text: "text 0", - root: 7, - }, - CreateTextNode { - text: "text 1", - root: 8, - }, + CreateTextNode { text: "text 0", root: 7 }, + CreateTextNode { text: "text 1", root: 8 }, ReplaceWith { root: 6, m: 2 }, ] ); diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 6b7805261..87d108094 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -152,12 +152,7 @@ fn free_works_on_root_props() { } } - let mut dom = new_dom( - app, - Custom { - val: String::from("asd"), - }, - ); + let mut dom = new_dom(app, Custom { val: String::from("asd") }); dom.rebuild(); } diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 3f04aa154..38195ccd8 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -29,10 +29,7 @@ fn shared_state_test() { assert_eq!( edits, [ - CreateTextNode { - root: 1, - text: "Hello, world!" - }, + CreateTextNode { root: 1, text: "Hello, world!" }, AppendChildren { many: 1 }, ] ); diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 5d1dbcdf8..83b4ff35f 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -68,15 +68,9 @@ fn conditional_rendering() { mutations.edits, [ CreateElement { root: 1, tag: "h1" }, - CreateTextNode { - root: 2, - text: "hello" - }, + CreateTextNode { root: 2, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { - root: 3, - tag: "span" - }, + CreateElement { root: 3, tag: "span" }, CreateTextNode { root: 4, text: "a" }, AppendChildren { many: 1 }, CreatePlaceholder { root: 5 }, From 0820e18d3dd1aac2f713251dfaddc6a8ebb041d0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 02:49:37 -0500 Subject: [PATCH 050/103] feat: make component name accessible --- packages/core-macro/src/rsx/component.rs | 3 +++ packages/core/src/nodes.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 5257022a4..4e9d449cf 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -135,11 +135,14 @@ impl ToTokens for Component { None => quote! { None }, }; + let fn_name = self.name.segments.last().unwrap().ident.to_string(); + tokens.append_all(quote! { __cx.component( #name, #builder, #key_token, + #fn_name ) }) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index b18aeaf2d..1ce39a682 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -385,6 +385,7 @@ pub struct VComponent<'src> { pub scope: Cell>, pub can_memoize: bool, pub user_fc: *const (), + pub fn_name: &'static str, pub props: RefCell>>, } @@ -549,6 +550,7 @@ impl<'a> NodeFactory<'a> { component: fn(Scope<'a, P>) -> Element, props: P, key: Option, + fn_name: &'static str, ) -> VNode<'a> where P: Properties + 'a, @@ -559,6 +561,7 @@ impl<'a> NodeFactory<'a> { can_memoize: P::IS_STATIC, user_fc: component as *const (), originator: self.scope.scope_id(), + fn_name, props: RefCell::new(Some(Box::new(VComponentProps { // local_props: RefCell::new(Some(props)), // heap_props: RefCell::new(None), From a4ea0ba4fe3b615179b01fab3c4704a938d5a5e4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 02:52:40 -0500 Subject: [PATCH 051/103] chore: undo dirty_scopes rename --- packages/core/src/mutations.rs | 6 +++--- packages/core/src/virtual_dom.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 5af727154..bd94d0b71 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -17,7 +17,7 @@ use std::{any::Any, fmt::Debug}; /// Mutations are the only link between the RealDOM and the VirtualDOM. pub struct Mutations<'a> { pub edits: Vec>, - pub diffed_scopes: FxHashSet, + pub dirty_scopes: FxHashSet, pub refs: Vec>, } @@ -118,7 +118,7 @@ impl<'a> Mutations<'a> { Self { edits: Vec::new(), refs: Vec::new(), - diffed_scopes: Default::default(), + dirty_scopes: Default::default(), } } @@ -223,7 +223,7 @@ impl<'a> Mutations<'a> { } pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) { - self.diffed_scopes.insert(scope); + self.dirty_scopes.insert(scope); } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9c4cd4018..b273e6239 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -480,8 +480,8 @@ impl VirtualDom { let AsyncDiffState { mutations, .. } = diff_state; - log::debug!("succesffuly resolved scopes {:?}", mutations.diffed_scopes); - for scope in &mutations.diffed_scopes { + log::debug!("succesffuly resolved scopes {:?}", mutations.dirty_scopes); + for scope in &mutations.dirty_scopes { self.dirty_scopes.remove(scope); } From 9dda7b168b15e31f739780c04b772d9dfa09066e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 03:04:47 -0500 Subject: [PATCH 052/103] chore: clean up scopes --- packages/core/src/scopes.rs | 7 +------ packages/core/src/virtual_dom.rs | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index c6c0cb846..b90ff6f6e 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -213,9 +213,7 @@ impl ScopeArena { } pub fn collect_garbage(&self, id: ElementId) { - let node = self.nodes.borrow_mut().get(id.0).unwrap().clone(); - // let node = self.nodes.borrow_mut().remove(id.0); - // log::debug!("collecting garbage for {:?}, {:?}", id, unsafe { &*node }); + self.nodes.borrow_mut().remove(id.0); } /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from @@ -231,8 +229,6 @@ impl ScopeArena { /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// be dropped. pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { - // log::trace!("Ensuring drop safety for scope {:?}", scope_id); - if let Some(scope) = self.get_scope(scope_id) { let mut items = scope.items.borrow_mut(); @@ -243,7 +239,6 @@ impl ScopeArena { if let Some(scope_id) = comp.scope.get() { self.ensure_drop_safety(scope_id); } - drop(comp.props.take()); }); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index b273e6239..670613dd4 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -449,6 +449,7 @@ impl VirtualDom { /// apply_mutations(mutations); /// } /// ``` + #[allow(unused)] pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec { let mut committed_mutations = vec![]; @@ -487,13 +488,12 @@ impl VirtualDom { committed_mutations.push(mutations); + // todo: pause the diff machine // if diff_state.work(&mut deadline) { // let DiffState { mutations, .. } = diff_state; - // for scope in &mutations.dirty_scopes { // self.dirty_scopes.remove(scope); // } - // committed_mutations.push(mutations); // } else { // // leave the work in an incomplete state From 120ee1836812f0ebf5c82685817f0866aee25f45 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 03:22:34 -0500 Subject: [PATCH 053/103] chore: improve debugability of fcptrs and vcomponents this commit changes the fc slot type from a thin pointer to a c pointer. this lets us provide frame inspection of components using backtrace. --- packages/core/src/nodes.rs | 17 ++++++++++++----- packages/core/src/scopes.rs | 6 +++--- packages/core/src/virtual_dom.rs | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1ce39a682..bc76e6bb9 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{Element, Properties, Scope, ScopeId, ScopeState}, + innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, lazynodes::LazyNodes, AnyEvent, Component, }; @@ -177,11 +177,18 @@ impl Debug for VNode<'_> { .field("children", &el.children) .finish(), VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text), - VNode::Placeholder(_) => write!(s, "VNode::VPlaceholder"), + VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id), VNode::Fragment(frag) => { write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) } - VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc), + VNode::Component(comp) => s + .debug_struct("VNode::VComponent") + .field("name", &comp.fn_name) + .field("fnptr", &comp.user_fc) + .field("key", &comp.key) + .field("scope", &comp.scope) + .field("originator", &comp.originator) + .finish(), } } } @@ -384,7 +391,7 @@ pub struct VComponent<'src> { pub originator: ScopeId, pub scope: Cell>, pub can_memoize: bool, - pub user_fc: *const (), + pub user_fc: ComponentPtr, pub fn_name: &'static str, pub props: RefCell>>, } @@ -559,7 +566,7 @@ impl<'a> NodeFactory<'a> { key: key.map(|f| self.raw_text(f).0), scope: Default::default(), can_memoize: P::IS_STATIC, - user_fc: component as *const (), + user_fc: component as ComponentPtr, originator: self.scope.scope_id(), fn_name, props: RefCell::new(Some(Box::new(VComponentProps { diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 77c648d18..2faa47dab 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -13,7 +13,7 @@ use std::{ rc::Rc, }; -pub(crate) type FcSlot = *const (); +pub(crate) type ComponentPtr = *mut std::os::raw::c_void; pub(crate) struct Heuristic { hook_arena_size: usize, @@ -28,7 +28,7 @@ pub(crate) struct ScopeArena { pub scope_gen: Cell, pub bump: Bump, pub scopes: RefCell>, - pub heuristics: RefCell>, + pub heuristics: RefCell>, pub free_scopes: RefCell>, pub nodes: RefCell>>, pub tasks: Rc, @@ -82,7 +82,7 @@ impl ScopeArena { pub(crate) fn new_with_key( &self, - fc_ptr: *const (), + fc_ptr: ComponentPtr, vcomp: Box, parent_scope: Option, container: ElementId, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 4e426caad..0bd93335f 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -212,7 +212,7 @@ impl VirtualDom { let scopes = ScopeArena::new(channel.0.clone()); scopes.new_with_key( - root as *const _, + root as ComponentPtr, Box::new(VComponentProps { props: root_props, memo: |_a, _b| unreachable!("memo on root will neve be run"), From b4697fc9f93377c497a8eb25fa56c4e3c347c4ef Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 03:27:43 -0500 Subject: [PATCH 054/103] chore: clean up some more of the scopes file --- packages/core/src/diff.rs | 4 ++-- packages/core/src/scopes.rs | 34 +++++++------------------------- packages/core/src/virtual_dom.rs | 6 +++--- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index db898f194..572ad7f66 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -2,7 +2,7 @@ use crate::innerlude::*; use fxhash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -pub(crate) struct AsyncDiffState<'bump> { +pub(crate) struct DiffState<'bump> { pub(crate) scopes: &'bump ScopeArena, pub(crate) mutations: Mutations<'bump>, pub(crate) force_diff: bool, @@ -10,7 +10,7 @@ pub(crate) struct AsyncDiffState<'bump> { pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, } -impl<'b> AsyncDiffState<'b> { +impl<'b> DiffState<'b> { pub fn new(scopes: &'b ScopeArena) -> Self { Self { scopes, diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index b90ff6f6e..ea935b433 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -186,7 +186,7 @@ impl ScopeArena { let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() }; scope.reset(); - // self.free_scopes.borrow_mut().push(scope); + self.free_scopes.borrow_mut().push(scope); Some(()) } @@ -202,14 +202,11 @@ impl ScopeArena { } pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) { - let node = unsafe { extend_vnode(node) }; - - let mut nodes = self.nodes.borrow_mut(); - let entry = nodes.get_mut(id.0); - match entry { - Some(_node) => *_node = node, - None => panic!("cannot update node {}", id), - } + *self + .nodes + .borrow_mut() + .get_mut(id.0) + .expect("node to exist if it's being updated") = unsafe { extend_vnode(node) }; } pub fn collect_garbage(&self, id: ElementId) { @@ -259,24 +256,13 @@ impl ScopeArena { // todo: we *know* that this is aliased by the contents of the scope itself let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") }; - // if cfg!(debug_assertions) { - // log::debug!("running scope {:?} symbol: {:?}", id, scope.fnptr); - - // todo: resolve frames properly - backtrace::resolve(scope.fnptr, |symbol| { - // backtrace::resolve(scope.fnptr as *mut std::os::raw::c_void, |symbol| { - // panic!("asd"); - // log::trace!("Running scope {:?}, ptr {:?}", id, scope.fnptr); - log::debug!("running scope {:?} symbol: {:?}", id, symbol.name()); - }); - // } + log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr); // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held // - All children nodes that rely on &mut T are replaced with a new reference scope.hook_idx.set(0); - // book keeping to ensure safety around the borrowed data { // Safety: // - We've dropped all references to the wip bump frame with "ensure_drop_safety" @@ -289,8 +275,6 @@ impl ScopeArena { debug_assert!(items.borrowed_props.is_empty()); } - // safety: this is definitely not dropped - /* If the component returns None, then we fill in a placeholder node. This will wipe what was there. An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks. @@ -332,14 +316,10 @@ impl ScopeArena { while let Some(id) = cur_el.take() { if let Some(el) = nodes.get(id.0) { - log::trace!("Found valid receiver element"); - let real_el = unsafe { &**el }; if let VNode::Element(real_el) = real_el { for listener in real_el.listeners.borrow().iter() { if listener.event == event.name { - log::trace!("Found valid receiver event"); - if state.canceled.get() { // stop bubbling if canceled break; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 5af569de1..a7092104c 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -2,7 +2,7 @@ //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. -use crate::diff::AsyncDiffState as DiffState; +use crate::diff::DiffState; use crate::innerlude::*; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{future::poll_fn, StreamExt}; @@ -455,7 +455,7 @@ impl VirtualDom { while !self.dirty_scopes.is_empty() { let scopes = &self.scopes; - let mut diff_state = AsyncDiffState::new(scopes); + let mut diff_state = DiffState::new(scopes); let mut ran_scopes = FxHashSet::default(); @@ -479,7 +479,7 @@ impl VirtualDom { diff_state.diff_scope(scopeid); - let AsyncDiffState { mutations, .. } = diff_state; + let DiffState { mutations, .. } = diff_state; log::debug!("succesffuly resolved scopes {:?}", mutations.dirty_scopes); for scope in &mutations.dirty_scopes { From 923fb0701d91eafb9f7225ba376154b072728645 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 03:34:52 -0500 Subject: [PATCH 055/103] fix: clippy --- packages/core/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index dba4effbb..e81ddca87 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -12,7 +12,6 @@ pub(crate) mod util; pub(crate) mod virtual_dom; pub(crate) mod innerlude { - pub(crate) use crate::diff::*; pub use crate::events::*; pub use crate::lazynodes::*; pub use crate::mutations::*; From 366cf758870f6bc54c36acfecfbda261ed28bebf Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 31 Jan 2022 19:39:44 +0800 Subject: [PATCH 056/103] feat: add `use_desktop_context` hook --- examples/borderless.rs | 10 +++------- packages/desktop/src/desktop_context.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/borderless.rs b/examples/borderless.rs index e6cd4eda1..15756503e 100644 --- a/examples/borderless.rs +++ b/examples/borderless.rs @@ -1,5 +1,4 @@ use dioxus::prelude::*; -use dioxus_desktop::desktop_context::DesktopContext; fn main() { dioxus::desktop::launch_cfg(app, |cfg| { @@ -8,16 +7,13 @@ fn main() { } fn app(cx: Scope) -> Element { - let desktop = cx.consume_context::().unwrap(); - - let drag = desktop.clone(); - let close = desktop.clone(); + let desktop = dioxus::desktop::desktop_context::use_desktop_context(&cx); cx.render(rsx!( link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } header { class: "text-gray-400 bg-gray-900 body-font", - onmousedown: move |_| drag.drag_window(), + onmousedown: move |_| desktop.drag_window(), div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center", a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0", @@ -33,7 +29,7 @@ fn app(cx: Scope) -> Element { button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", onmousedown: |evt| evt.cancel_bubble(), - onclick: move |_| close.close(), + onclick: move |_| desktop.close(), "Close" } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 0a1fa2b02..0e2fbdddd 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,3 +1,6 @@ +use std::rc::Rc; + +use dioxus_core::ScopeState; use wry::application::event_loop::EventLoopProxy; use crate::UserWindowEvent; @@ -50,3 +53,10 @@ impl DesktopContext { let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); } } + +/// use this function can get the `DesktopContext` context. +pub fn use_desktop_context(cx: &ScopeState) -> &Rc { + cx.use_hook(|_| cx.consume_context::()) + .as_ref() + .unwrap() +} From 7ca92be015090ff2f4c44d778f10e97edcf90a92 Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Mon, 31 Jan 2022 19:44:40 +0800 Subject: [PATCH 057/103] feat: add `focus` api --- packages/desktop/src/desktop_context.rs | 5 +++++ packages/desktop/src/lib.rs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 0e2fbdddd..a41712904 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -52,6 +52,11 @@ impl DesktopContext { pub fn close(&self) { let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); } + + /// set window to focus + pub fn focus(&self) { + let _ = self.proxy.send_event(UserWindowEvent::FocusWindow); + } } /// use this function can get the `DesktopContext` context. diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 45314df34..eb76ce63d 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -313,6 +313,12 @@ pub fn launch_with_props( window.set_maximized(state); } } + UserWindowEvent::FocusWindow => { + for webview in desktop.webviews.values() { + let window = webview.window(); + window.set_focus(); + } + } } } Event::MainEventsCleared => {} @@ -329,6 +335,7 @@ pub enum UserWindowEvent { Update, DragWindow, CloseWindow, + FocusWindow, Minimize(bool), Maximize(bool), } From 06418f73db4bf3721d2d9ca9bf2fdd184663158d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 12:29:11 -0500 Subject: [PATCH 058/103] fix: element stack not being updated properly --- packages/core/src/diff.rs | 2 ++ packages/core/src/scopes.rs | 15 +++++++------- packages/router/src/service.rs | 37 +--------------------------------- 3 files changed, 10 insertions(+), 44 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 572ad7f66..1a53b01c4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -166,6 +166,8 @@ impl<'b> DiffState<'b> { self.create_and_append_children(children); } + self.element_stack.pop(); + 1 } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index ea935b433..00c3024bf 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -96,10 +96,9 @@ impl ScopeArena { // Get the height of the scope let height = parent_scope - .map(|id| self.get_scope(id).map(|scope| scope.height)) + .map(|id| self.get_scope(id).map(|scope| scope.height + 1)) .flatten() - .unwrap_or_default() - + 1; + .unwrap_or_default(); let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten(); @@ -202,11 +201,8 @@ impl ScopeArena { } pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) { - *self - .nodes - .borrow_mut() - .get_mut(id.0) - .expect("node to exist if it's being updated") = unsafe { extend_vnode(node) }; + let node = unsafe { extend_vnode(node) }; + *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node; } pub fn collect_garbage(&self, id: ElementId) { @@ -317,9 +313,12 @@ impl ScopeArena { while let Some(id) = cur_el.take() { if let Some(el) = nodes.get(id.0) { let real_el = unsafe { &**el }; + log::debug!("looking for listener on {:?}", real_el); + if let VNode::Element(real_el) = real_el { for listener in real_el.listeners.borrow().iter() { if listener.event == event.name { + log::debug!("calling listener {:?}", listener.event); if state.canceled.get() { // stop bubbling if canceled break; diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index b6d8ba11b..a4ab4dc0d 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -62,12 +62,10 @@ impl RouterService { root_found.set(None); // checking if the route is valid is cheap, so we do it for (slot, root) in slots.borrow_mut().iter().rev() { - // log::trace!("regenerating slot {:?} for root '{}'", slot, root); regen_route(*slot); } for listener in onchange_listeners.borrow_mut().iter() { - // log::trace!("regenerating listener {:?}", listener); regen_route(*listener); } @@ -91,31 +89,24 @@ impl RouterService { } pub fn push_route(&self, route: &str) { - // log::trace!("Pushing route: {}", route); self.history.borrow_mut().push(route); } pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) { let clean = clean_route(route); - // log::trace!("Registered route '{}' with scope id {:?}", clean, scope); self.slots.borrow_mut().push((scope, clean)); } pub fn should_render(&self, scope: ScopeId) -> bool { - // log::trace!("Should render scope id {:?}?", scope); if let Some(root_id) = self.root_found.get() { - // log::trace!(" we already found a root with scope id {:?}", root_id); if root_id == scope { - // log::trace!(" yes - it's a match"); return true; } - // log::trace!(" no - it's not a match"); return false; } let location = self.history.borrow().location(); let path = location.path(); - // log::trace!(" current path is '{}'", path); let roots = self.slots.borrow(); @@ -124,23 +115,15 @@ impl RouterService { // fallback logic match root { Some((id, route)) => { - // log::trace!( - // " matched given scope id {:?} with route root '{}'", - // scope, - // route, - // ); if let Some(params) = route_matches_path(route, path) { - // log::trace!(" and it matches the current path '{}'", path); self.root_found.set(Some(*id)); *self.cur_path_params.borrow_mut() = params; true } else { if route == "" { - // log::trace!(" and the route is the root, so we will use that without a better match"); self.root_found.set(Some(*id)); true } else { - // log::trace!(" and the route '{}' is not the root nor does it match the current path", route); false } } @@ -158,12 +141,10 @@ impl RouterService { } pub fn subscribe_onchange(&self, id: ScopeId) { - // log::trace!("Subscribing onchange for scope id {:?}", id); self.onchange_listeners.borrow_mut().insert(id); } pub fn unsubscribe_onchange(&self, id: ScopeId) { - // log::trace!("Subscribing onchange for scope id {:?}", id); self.onchange_listeners.borrow_mut().remove(&id); } } @@ -186,36 +167,20 @@ fn route_matches_path(route: &str, path: &str) -> Option let route_pieces = route.split('/').collect::>(); let path_pieces = clean_path(path).split('/').collect::>(); - // log::trace!( - // " checking route pieces {:?} vs path pieces {:?}", - // route_pieces, - // path_pieces, - // ); - if route_pieces.len() != path_pieces.len() { - // log::trace!(" the routes are different lengths"); return None; } let mut matches = HashMap::new(); for (i, r) in route_pieces.iter().enumerate() { - // log::trace!(" checking route piece '{}' vs path", r); // If this is a parameter then it matches as long as there's // _any_thing in that spot in the path. if r.starts_with(':') { - // log::trace!( - // " route piece '{}' starts with a colon so it matches anything", - // r, - // ); let param = &r[1..]; matches.insert(param.to_string(), path_pieces[i].to_string()); continue; } - // log::trace!( - // " route piece '{}' must be an exact match for path piece '{}'", - // r, - // path_pieces[i], - // ); + if path_pieces[i] != *r { return None; } From c4e6496d9d7064d97359edd7ae267d4bc6b98843 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 12:49:21 -0500 Subject: [PATCH 059/103] chore: enable a pedantic clippy on the diffing algorithm --- packages/core/src/diff.rs | 130 ++++++++++++++++--------------------- packages/core/src/nodes.rs | 7 -- 2 files changed, 56 insertions(+), 81 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 1a53b01c4..d40f49934 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,4 +1,10 @@ -use crate::innerlude::*; +#![warn(clippy::pedantic)] +#![allow(clippy::cast_possible_truncation)] + +use crate::innerlude::{ + AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode, + VPlaceholder, VText, +}; use fxhash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; @@ -33,7 +39,7 @@ impl<'b> DiffState<'b> { } pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { - use VNode::*; + use VNode::{Component, Element, Fragment, Placeholder, Text}; match (old_node, new_node) { // Check the most common cases first // these are *actual* elements, not wrappers around lists @@ -61,7 +67,7 @@ impl<'b> DiffState<'b> { if let Some(root) = old.id.get() { self.scopes.update_node(new_node, root); - new.id.set(Some(root)) + new.id.set(Some(root)); } } @@ -70,7 +76,7 @@ impl<'b> DiffState<'b> { log::trace!("skipping node diff - element are the sames"); return; } - self.diff_element_nodes(old, new, old_node, new_node) + self.diff_element_nodes(old, new, old_node, new_node); } // These two sets are pointers to nodes but are not actually nodes themselves @@ -79,7 +85,7 @@ impl<'b> DiffState<'b> { log::trace!("skipping node diff - placeholder are the sames"); return; } - self.diff_component_nodes(old_node, new_node, *old, *new) + self.diff_component_nodes(old_node, new_node, *old, *new); } (Fragment(old), Fragment(new)) => { @@ -87,7 +93,7 @@ impl<'b> DiffState<'b> { log::trace!("skipping node diff - fragment are the sames"); return; } - self.diff_fragment_nodes(old, new) + self.diff_fragment_nodes(old, new); } // Anything else is just a basic replace and create @@ -136,17 +142,15 @@ impl<'b> DiffState<'b> { .. } = element; - // set the parent ID for event bubbling - // self.stack.instructions.push(DiffInstruction::PopElement); - let parent = self.element_stack.last().unwrap(); parent_id.set(Some(*parent)); // set the id of the element let real_id = self.scopes.reserve_node(node); - self.element_stack.push(real_id); dom_id.set(Some(real_id)); + self.element_stack.push(real_id); + self.mutations.create_element(tag_name, *namespace, real_id); if let Some(cur_scope_id) = self.current_scope() { @@ -155,7 +159,7 @@ impl<'b> DiffState<'b> { self.mutations.new_event_listener(listener, cur_scope_id); } } else { - log::warn!("create element called with no scope on the stack - this is an error for a live dom"); + log::trace!("create element called with no scope on the stack - this is an error for a live dom"); } for attr in *attributes { @@ -198,7 +202,7 @@ impl<'b> DiffState<'b> { new_idx }; - log::info!( + log::trace!( "created component \"{}\", id: {:?} parent {:?} orig: {:?}", vcomponent.fn_name, new_idx, @@ -209,14 +213,11 @@ impl<'b> DiffState<'b> { // Actually initialize the caller's slot with the right address vcomponent.scope.set(Some(new_idx)); - match vcomponent.can_memoize { - true => { - // todo: implement promotion logic. save us from boxing props that we don't need - } - false => { - // track this component internally so we know the right drop order - } - } + // if vcomponent.can_memoize { + // // todo: implement promotion logic. save us from boxing props that we don't need + // } else { + // // track this component internally so we know the right drop order + // } self.enter_scope(new_idx); @@ -278,7 +279,7 @@ impl<'b> DiffState<'b> { self.mutations.remove_attribute(attribute, root.as_u64()); } for attribute in new.attributes { - self.mutations.set_attribute(attribute, root.as_u64()) + self.mutations.set_attribute(attribute, root.as_u64()); } } @@ -318,9 +319,7 @@ impl<'b> DiffState<'b> { let created = self.create_children(new.children); self.mutations.append_children(created as u32); } - (_, _) => { - self.diff_children(old.children, new.children); - } + (_, _) => self.diff_children(old.children, new.children), }; } @@ -332,14 +331,8 @@ impl<'b> DiffState<'b> { new: &'b VComponent<'b>, ) { let scope_addr = old.scope.get().unwrap(); - log::trace!( - "diff_component_nodes. old: {:#?} new: {:#?}", - old_node, - new_node - ); if std::ptr::eq(old, new) { - log::trace!("skipping component diff - component is the sames"); return; } @@ -397,7 +390,6 @@ impl<'b> DiffState<'b> { self.leave_scope(); } else { - log::debug!("scope stack is {:#?}", self.scope_stack); self.replace_node(old_node, new_node); } } @@ -405,14 +397,12 @@ impl<'b> DiffState<'b> { fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { // This is the case where options or direct vnodes might be used. // In this case, it's faster to just skip ahead to their diff - // if old.children.len() == 1 && new.children.len() == 1 { - // if std::ptr::eq(old, new) { - // log::debug!("skipping fragment diff - fragment is the same"); - // return; - // } - // self.diff_node(&old.children[0], &new.children[0]); - // return; - // } + if old.children.len() == 1 && new.children.len() == 1 { + if !std::ptr::eq(old, new) { + self.diff_node(&old.children[0], &new.children[0]); + } + return; + } debug_assert!(!old.children.is_empty()); debug_assert!(!new.children.is_empty()); @@ -441,7 +431,6 @@ impl<'b> DiffState<'b> { // to an element, and appending makes sense. fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { if std::ptr::eq(old, new) { - log::debug!("skipping fragment diff - fragment is the same {:?}", old); return; } @@ -481,24 +470,19 @@ impl<'b> DiffState<'b> { // // the change list stack is in the same state when this function returns. fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + use std::cmp::Ordering; + // Handled these cases in `diff_children` before calling this function. debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); - use std::cmp::Ordering; match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..], true), Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), Ordering::Equal => {} } - // panic!( - // "diff_children: new_is_keyed: {:#?}, old_is_keyed: {:#?}. stack: {:#?}, oldptr: {:#?}, newptr: {:#?}", - // old, new, self.scope_stack, old as *const _, new as *const _ - // ); - for (new, old) in new.iter().zip(old.iter()) { - // log::debug!("diffing nonkeyed {:#?} {:#?}", old, new); self.diff_node(old, new); } } @@ -650,6 +634,7 @@ impl<'b> DiffState<'b> { // This function will load the appropriate nodes onto the stack and do diffing in place. // // Upon exit from this function, it will be restored to that same self. + #[allow(clippy::too_many_lines)] fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { /* 1. Map the old keys into a numerical ordering based on indices. @@ -675,8 +660,8 @@ impl<'b> DiffState<'b> { // 0. Debug sanity checks // Should have already diffed the shared-key prefixes and suffixes. - debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key())); - debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key())); + debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key)); + debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key)); // 1. Map the old keys into a numerical ordering based on indices. // 2. Create a map of old key to its index @@ -741,7 +726,7 @@ impl<'b> DiffState<'b> { lis_sequence.pop(); } - for idx in lis_sequence.iter() { + for idx in &lis_sequence { self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]); } @@ -775,7 +760,6 @@ impl<'b> DiffState<'b> { if last - next > 1 { for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { let new_idx = idx + next + 1; - let old_index = new_index_to_old_index[new_idx]; if old_index == u32::MAX as usize { nodes_created += self.create_node(new_node); @@ -816,7 +800,6 @@ impl<'b> DiffState<'b> { } fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { - log::debug!("Replacing node\n old: {:?}\n new: {:?}", old, new); let nodes_created = self.create_node(new); self.replace_inner(old, nodes_created); } @@ -848,7 +831,7 @@ impl<'b> DiffState<'b> { } VNode::Component(c) => { - log::info!("Replacing component {:?}", old); + log::trace!("Replacing component {:?}", old); let scope_id = c.scope.get().unwrap(); let node = self.scopes.fin_head(scope_id); @@ -856,11 +839,11 @@ impl<'b> DiffState<'b> { self.replace_inner(node, nodes_created); - log::info!("Replacing component x2 {:?}", old); + log::trace!("Replacing component x2 {:?}", old); // we can only remove components if they are actively being diffed if self.scope_stack.contains(&c.originator) { - log::debug!("Removing component {:?}", old); + log::trace!("Removing component {:?}", old); self.scopes.try_remove(scope_id).unwrap(); } @@ -913,7 +896,7 @@ impl<'b> DiffState<'b> { let root = self.scopes.root_node(scope_id); self.remove_nodes([root], gen_muts); - log::info!( + log::trace!( "trying to remove scope {:?}, stack is {:#?}, originator is {:?}", scope_id, self.scope_stack, @@ -922,7 +905,7 @@ impl<'b> DiffState<'b> { // we can only remove this node if the originator is actively if self.scope_stack.contains(&c.originator) { - log::debug!("I am allowed to remove component because scope stack contains originator. Scope stack: {:#?} {:#?} {:#?}", self.scope_stack, c.originator, c.scope); + log::trace!("I am allowed to remove component because scope stack contains originator. Scope stack: {:#?} {:#?} {:#?}", self.scope_stack, c.originator, c.scope); self.scopes.try_remove(scope_id).unwrap(); } self.leave_scope(); @@ -970,15 +953,12 @@ impl<'b> DiffState<'b> { fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option { let mut search_node = Some(vnode); - loop { match &search_node.take().unwrap() { VNode::Text(t) => break t.id.get(), VNode::Element(t) => break t.id.get(), VNode::Placeholder(t) => break t.id.get(), - VNode::Fragment(frag) => { - search_node = frag.children.last(); - } + VNode::Fragment(frag) => search_node = frag.children.last(), VNode::Component(el) => { let scope_id = el.scope.get().unwrap(); search_node = Some(self.scopes.root_node(scope_id)); @@ -989,20 +969,16 @@ impl<'b> DiffState<'b> { fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option { let mut search_node = Some(vnode); - loop { - match &search_node.take().unwrap() { - // the ones that have a direct id - VNode::Fragment(frag) => { - search_node = Some(&frag.children[0]); - } - VNode::Component(el) => { - let scope_id = el.scope.get().unwrap(); - search_node = Some(self.scopes.root_node(scope_id)); - } + match &search_node.take().expect("search node to have an ID") { VNode::Text(t) => break t.id.get(), VNode::Element(t) => break t.id.get(), VNode::Placeholder(t) => break t.id.get(), + VNode::Fragment(frag) => search_node = Some(&frag.children[0]), + VNode::Component(el) => { + let scope = el.scope.get().expect("element to have a scope assigned"); + search_node = Some(self.scopes.root_node(scope)); + } } } } @@ -1015,20 +991,26 @@ impl<'b> DiffState<'b> { 1 } - VNode::Fragment(_) | VNode::Component(_) => { - // + VNode::Fragment(frag) => { let mut added = 0; - for child in node.children() { + for child in frag.children { added += self.push_all_nodes(child); } added } + VNode::Component(c) => { + let scope_id = c.scope.get().unwrap(); + let root = self.scopes.root_node(scope_id); + self.push_all_nodes(root) + } + VNode::Element(el) => { let mut num_on_stack = 0; for child in el.children.iter() { num_on_stack += self.push_all_nodes(child); } + self.mutations.push_root(el.id.get().unwrap()); num_on_stack + 1 diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index b849819f0..7a36f58ce 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -147,13 +147,6 @@ impl<'src> VNode<'src> { } } - pub(crate) fn children(&self) -> &[VNode<'src>] { - match &self { - VNode::Fragment(f) => f.children, - _ => &[], - } - } - // Create an "owned" version of the vnode. pub fn decouple(&self) -> VNode<'src> { match *self { From fb75948363fdcc182292431e2c8706d883cc07c1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 14:33:25 -0500 Subject: [PATCH 060/103] fix: usestate modify panic --- packages/hooks/src/usestate.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index bce5b2b88..d77d7ceaa 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -39,16 +39,17 @@ pub fn use_state<'a, T: 'static>( let setter = Rc::new({ crate::to_owned![update_callback, slot]; move |new| { - let mut slot = slot.borrow_mut(); + { + let mut slot = slot.borrow_mut(); - // if there's only one reference (weak or otherwise), we can just swap the values - // Typically happens when the state is set multiple times - we don't want to create a new Rc for each new value - if let Some(val) = Rc::get_mut(&mut slot) { - *val = new; - } else { - *slot = Rc::new(new); + // if there's only one reference (weak or otherwise), we can just swap the values + // Typically happens when the state is set multiple times - we don't want to create a new Rc for each new value + if let Some(val) = Rc::get_mut(&mut slot) { + *val = new; + } else { + *slot = Rc::new(new); + } } - update_callback(); } }); @@ -92,7 +93,7 @@ impl UseState { /// } /// }) /// } - /// ``` + /// ``` #[must_use] pub fn current(&self) -> Rc { self.slot.borrow().clone() @@ -137,10 +138,10 @@ impl UseState { /// # use dioxus_hooks::*; /// fn component(cx: Scope) -> Element { /// let (value, set_value) = use_state(&cx, || 0); - /// + /// /// // to increment the value /// set_value.modify(|v| v + 1); - /// + /// /// // usage in async /// cx.spawn({ /// let set_value = set_value.to_owned(); @@ -153,8 +154,10 @@ impl UseState { /// } /// ``` pub fn modify(&self, f: impl FnOnce(&T) -> T) { - let current = self.slot.borrow(); - let new_val = f(current.as_ref()); + let new_val = { + let current = self.slot.borrow(); + f(current.as_ref()) + }; (self.setter)(new_val); } @@ -176,7 +179,7 @@ impl UseState { /// # use dioxus_hooks::*; /// fn component(cx: Scope) -> Element { /// let (value, set_value) = use_state(&cx, || 0); - /// + /// /// let as_rc = set_value.get(); /// assert_eq!(as_rc.as_ref(), &0); /// @@ -201,7 +204,7 @@ impl UseState { /// } /// }) /// } - /// ``` + /// ``` pub fn needs_update(&self) { (self.update_callback)(); } From 8badf90a03b1fba883bbca598a685e7d1ce367c2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 15:53:06 -0500 Subject: [PATCH 061/103] fix: dioxus web prevents default on in nested targets this commit fixes an issue where we used the event target to prevent default even if the target element wasn't an event handler. --- packages/web/src/dom.rs | 100 +++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 53 deletions(-) diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index ffbef9f4b..733e50e4b 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -28,17 +28,54 @@ impl WebsysDom { pub fn new(cfg: WebConfig, sender_callback: Rc) -> Self { // eventually, we just want to let the interpreter do all the work of decoding events into our event type let callback: Box = Box::new(move |event: &web_sys::Event| { - if let Ok(synthetic_event) = decode_trigger(event) { + let mut target = event + .target() + .expect("missing target") + .dyn_into::() + .expect("not a valid element"); + + let typ = event.type_(); + + let decoded: anyhow::Result = loop { + match target.get_attribute("data-dioxus-id").map(|f| f.parse()) { + Some(Ok(id)) => { + break Ok(UserEvent { + name: event_name_from_typ(&typ), + data: virtual_event_from_websys_event(event.clone()), + element: Some(ElementId(id)), + scope_id: None, + priority: dioxus_core::EventPriority::Medium, + }); + } + Some(Err(e)) => { + break Err(e.into()); + } + None => { + // walk the tree upwards until we actually find an event target + if let Some(parent) = target.parent_element() { + target = parent; + } else { + break Ok(UserEvent { + name: event_name_from_typ(&typ), + data: virtual_event_from_websys_event(event.clone()), + element: None, + scope_id: None, + priority: dioxus_core::EventPriority::Low, + }); + } + } + } + }; + + if let Ok(synthetic_event) = decoded { // Try to prevent default if the attribute is set - if let Some(target) = event.target() { - if let Some(node) = target.dyn_ref::() { - if let Some(name) = node.get_attribute("dioxus-prevent-default") { - if name == synthetic_event.name - || name.trim_start_matches("on") == synthetic_event.name - { - log::trace!("Preventing default"); - event.prevent_default(); - } + if let Some(node) = target.dyn_ref::() { + if let Some(name) = node.get_attribute("dioxus-prevent-default") { + if name == synthetic_event.name + || name.trim_start_matches("on") == synthetic_event.name + { + log::trace!("Preventing default"); + event.prevent_default(); } } } @@ -274,49 +311,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc anyhow::Result { - let mut target = event - .target() - .expect("missing target") - .dyn_into::() - .expect("not a valid element"); - - let typ = event.type_(); - - loop { - match target.get_attribute("data-dioxus-id").map(|f| f.parse()) { - Some(Ok(id)) => { - return Ok(UserEvent { - name: event_name_from_typ(&typ), - data: virtual_event_from_websys_event(event.clone()), - element: Some(ElementId(id)), - scope_id: None, - priority: dioxus_core::EventPriority::Medium, - }); - } - Some(Err(e)) => { - return Err(e.into()); - } - None => { - // walk the tree upwards until we actually find an event target - if let Some(parent) = target.parent_element() { - target = parent; - } else { - return Ok(UserEvent { - name: event_name_from_typ(&typ), - data: virtual_event_from_websys_event(event.clone()), - element: None, - scope_id: None, - priority: dioxus_core::EventPriority::Low, - }); - } - } - } - } -} - pub(crate) fn load_document() -> Document { web_sys::window() .expect("should have access to the Window") From f1fe8f1d2a02b53919ef0d107fca0f85619b89fc Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 1 Feb 2022 08:39:31 +0800 Subject: [PATCH 062/103] fix: change hooks name --- examples/borderless.rs | 8 ++++---- packages/desktop/src/desktop_context.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/borderless.rs b/examples/borderless.rs index 15756503e..2a1c007f2 100644 --- a/examples/borderless.rs +++ b/examples/borderless.rs @@ -7,13 +7,13 @@ fn main() { } fn app(cx: Scope) -> Element { - let desktop = dioxus::desktop::desktop_context::use_desktop_context(&cx); + let window = dioxus::desktop::desktop_context::use_window(&cx); cx.render(rsx!( link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } header { class: "text-gray-400 bg-gray-900 body-font", - onmousedown: move |_| desktop.drag_window(), + onmousedown: move |_| window.drag(), div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center", a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0", @@ -23,13 +23,13 @@ fn app(cx: Scope) -> Element { button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", onmousedown: |evt| evt.cancel_bubble(), - onclick: move |_| desktop.minimize(true), + onclick: move |_| window.minimize(true), "Minimize" } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", onmousedown: |evt| evt.cancel_bubble(), - onclick: move |_| desktop.close(), + onclick: move |_| window.close(), "Close" } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index a41712904..9c0404477 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -34,7 +34,7 @@ impl DesktopContext { /// ```rust /// onmousedown: move |_| { desktop.drag_window(); } /// ``` - pub fn drag_window(&self) { + pub fn drag(&self) { let _ = self.proxy.send_event(UserWindowEvent::DragWindow); } @@ -60,7 +60,7 @@ impl DesktopContext { } /// use this function can get the `DesktopContext` context. -pub fn use_desktop_context(cx: &ScopeState) -> &Rc { +pub fn use_window(cx: &ScopeState) -> &Rc { cx.use_hook(|_| cx.consume_context::()) .as_ref() .unwrap() From 6905bf98d75f90e5fb7cab863f150dd9aee189b1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Jan 2022 19:40:48 -0500 Subject: [PATCH 063/103] chore: clean up examples and fix link opening code --- examples/disabled.rs | 5 ---- examples/dog_app.rs | 4 +-- examples/file_explorer.rs | 1 - examples/filedragdrop.rs | 2 +- examples/framework_benchmark.rs | 4 +-- packages/desktop/src/interpreter.js | 42 +++++++++++++++-------------- packages/desktop/src/lib.rs | 1 + 7 files changed, 28 insertions(+), 31 deletions(-) diff --git a/examples/disabled.rs b/examples/disabled.rs index f72a8ff77..949c25369 100644 --- a/examples/disabled.rs +++ b/examples/disabled.rs @@ -18,11 +18,6 @@ fn app(cx: Scope) -> Element { disabled: "{disabled}", "lower button" } - - input { - value: "false", - } - } }) } diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 331574a75..a30f3e5e8 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -16,7 +16,7 @@ struct ListBreeds { } fn app(cx: Scope) -> Element { - let fut = use_future(&cx, || async move { + let breeds = use_future(&cx, || async move { reqwest::get("https://dog.ceo/api/breeds/list/all") .await .unwrap() @@ -26,7 +26,7 @@ fn app(cx: Scope) -> Element { let (breed, set_breed) = use_state(&cx, || None); - match fut.value() { + match breeds.value() { Some(Ok(breeds)) => cx.render(rsx! { div { h1 {"Select a dog breed!"} diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index fe5ab23bb..728812027 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -56,7 +56,6 @@ fn app(cx: Scope) -> Element { ) }) } - }) } diff --git a/examples/filedragdrop.rs b/examples/filedragdrop.rs index 523405b39..e5644e2a0 100644 --- a/examples/filedragdrop.rs +++ b/examples/filedragdrop.rs @@ -4,7 +4,7 @@ fn main() { dioxus::desktop::launch_with_props(app, (), |c| { c.with_file_drop_handler(|_w, e| { println!("{:?}", e); - false + true }) }); } diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 7cea4f5f7..8dba1cb91 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -17,9 +17,9 @@ impl Label { fn new_list(num: usize) -> Vec { let mut rng = SmallRng::from_entropy(); let mut labels = Vec::with_capacity(num); - for _ in 0..num { + for x in 0..num { labels.push(Label { - key: 0, + key: x, labels: [ ADJECTIVES.choose(&mut rng).unwrap(), COLOURS.choose(&mut rng).unwrap(), diff --git a/packages/desktop/src/interpreter.js b/packages/desktop/src/interpreter.js index 61ba649c7..503ea7247 100644 --- a/packages/desktop/src/interpreter.js +++ b/packages/desktop/src/interpreter.js @@ -200,14 +200,35 @@ export class Interpreter { let target = event.target; if (target != null) { let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + console.log("click", event); + console.log("clickeded", event.target); + console.log("clickeded", event.target.tagName); + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined) { + window.rpc.call("browser_open", { href }); + } + } + } + } + // walk the tree to find the real element while (realId == null && target.parentElement != null) { target = target.parentElement; realId = target.getAttribute(`data-dioxus-id`); } - const shouldPreventDefault = target.getAttribute( + + shouldPreventDefault = target.getAttribute( `dioxus-prevent-default` ); + let contents = serialize_event(event); if (shouldPreventDefault === `on${event.type}`) { event.preventDefault(); @@ -215,25 +236,6 @@ export class Interpreter { if (event.type == "submit") { event.preventDefault(); } - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if ( - href !== "" && - href !== null && - href !== undefined && - realId != null - ) { - window.rpc.call("browser_open", { - mounted_dom_id: parseInt(realId), - href, - }); - } - } - } - } if (realId == null) { return; } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index f3e370230..86928ef24 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -188,6 +188,7 @@ pub fn launch_with_props( let _ = proxy.send_event(UserWindowEvent::Update); } "browser_open" => { + println!("browser_open"); let data = req.params.unwrap(); log::trace!("Open browser: {:?}", data); if let Some(arr) = data.as_array() { From bead03596942089862b63d83e6d7514ddada946a Mon Sep 17 00:00:00 2001 From: mrxiaozhuox Date: Tue, 1 Feb 2022 08:43:54 +0800 Subject: [PATCH 064/103] feat: export `use_window` --- examples/borderless.rs | 2 +- packages/desktop/src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/borderless.rs b/examples/borderless.rs index 2a1c007f2..d52e9fb5a 100644 --- a/examples/borderless.rs +++ b/examples/borderless.rs @@ -7,7 +7,7 @@ fn main() { } fn app(cx: Scope) -> Element { - let window = dioxus::desktop::desktop_context::use_window(&cx); + let window = dioxus::desktop::use_window(&cx); cx.render(rsx!( link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index eb76ce63d..524e42da4 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -56,6 +56,7 @@ pub mod escape; pub mod events; use cfg::DesktopConfig; +pub use desktop_context::use_window; use desktop_context::DesktopContext; use dioxus_core::*; use std::{ From fa020c53e48b7770afbff8f24bc25a70d19ed58f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 01:57:21 -0500 Subject: [PATCH 065/103] tweak: use 2018 rust syntax instead of 2022 for formatting --- packages/desktop/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index eb4657c52..22acc4f85 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -248,7 +248,7 @@ pub fn launch_with_props( // do not let path searching to go two layers beyond the caller level let data = read(path_buf)?; - let meta = format!("{mime}"); + let meta = format!("{}", mime); wry::http::ResponseBuilder::new().mimetype(&meta).body(data) } From e02dfc3324fdb808f6390d2d6942fb727220a329 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 02:05:54 -0500 Subject: [PATCH 066/103] fix: change use_state to be clone --- packages/hooks/src/usestate.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index d77d7ceaa..7e8892be9 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -275,10 +275,8 @@ impl UseState { } } -impl ToOwned for UseState { - type Owned = UseState; - - fn to_owned(&self) -> Self::Owned { +impl Clone for UseState { + fn clone(&self) -> Self { UseState { current_val: self.current_val.clone(), update_callback: self.update_callback.clone(), From d7968c987ff07134f480e9ab3869d518c1ecd554 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 20:40:29 +0100 Subject: [PATCH 067/103] wip: Makefile setup --- Makefile.toml | 42 +++++++++++++++++++++ packages/router/Cargo.toml | 56 ++++++++++++++-------------- packages/router/Makefile.toml | 10 +++++ packages/router/tests/route.rs | 68 ++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 Makefile.toml create mode 100644 packages/router/Makefile.toml create mode 100644 packages/router/tests/route.rs diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 000000000..23eeacea2 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,42 @@ +[config] +default_to_workspace = false +min_version = "0.32.4" + +[env] +CARGO_MAKE_CLIPPY_ARGS = "-- --deny=warnings" +CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true + +[config.modify_core_tasks] +namespace = "core" +private = true + +[tasks.tests] +category = "Testing" +dependencies = ["tests-setup"] +description = "Run all tests" +env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"]} +run_task = {name = ["test-flow"], fork = true} + +[tasks.tests-setup] +private = true +script = [ + """ + test_flags = array --headless --firefox + dioxus_test_features = set wasm_test + dioxus_test_flags = array_join ${test_flags} " " + echo "running tests with flags: ${dioxus_test_flags} and features: ${dioxus_test_features}" + set_env DIOXUS_TEST_FLAGS ${dioxus_test_flags} + set_env DIOXUS_TEST_FEATURES ${dioxus_test_features} + """, +] +script_runner = "@duckscript" + +[tasks.test-flow] +dependencies = ["test"] +private = true +workspace = true + +[tasks.test] +args = ["test", "--all-targets"] +command = "cargo" +private = true diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 1c6ed24d2..3bcb6a348 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -1,51 +1,51 @@ [package] -name = "dioxus-router" -version = "0.1.1" -edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" -license = "MIT/Apache-2.0" -repository = "https://github.com/DioxusLabs/dioxus/" -homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" +edition = "2018" +homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] +license = "MIT/Apache-2.0" +name = "dioxus-router" +repository = "https://github.com/DioxusLabs/dioxus/" +version = "0.1.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.1.9", default-features = false } -dioxus-html = { path = "../html", version = "^0.1.6", default-features = false } -dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } +dioxus-core = {path = "../core", version = "^0.1.9", default-features = false} +dioxus-core-macro = {path = "../core-macro", version = "^0.1.7"} +dioxus-html = {path = "../html", version = "^0.1.6", default-features = false} serde = "1" -url = "2.2.2" serde_urlencoded = "0.7" +url = "2.2.2" # for wasm -web-sys = { version = "0.3", features = [ - "Attr", - "Document", - "History", - "HtmlBaseElement", - "Event", - "NamedNodeMap", - "Url", - "UrlSearchParams", - "Window", -], optional = true } -wasm-bindgen = { version = "0.2", optional = true } -js-sys = { version = "0.3", optional = true } -gloo = { version = "0.5", optional = true } +gloo = {version = "0.5", optional = true} +js-sys = {version = "0.3", optional = true} log = "0.4.14" - +wasm-bindgen = {version = "0.2", optional = true} +web-sys = {version = "0.3", features = [ + "Attr", + "Document", + "History", + "HtmlBaseElement", + "Event", + "NamedNodeMap", + "Url", + "UrlSearchParams", + "Window", +], optional = true} [features] default = ["derive", "web"] -web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] +derive = [] desktop = [] mobile = [] -derive = [] +web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] [dev-dependencies] console_error_panic_hook = "0.1.7" -dioxus-web = { path = "../web" } +dioxus-web = {path = "../web"} log = "0.4.14" +wasm-bindgen-test = "0.3" wasm-logger = "0.2.0" diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml new file mode 100644 index 000000000..27a6c9244 --- /dev/null +++ b/packages/router/Makefile.toml @@ -0,0 +1,10 @@ +[tasks.test] +args = [ + "test", + "@@split(DIOXUS_TEST_FLAGS, )", + "--", + "--features", + "${DIOXUS_TEST_FEATURES}", +] +command = "wasm-pack" +extend = "core::wasm-pack-base" diff --git a/packages/router/tests/route.rs b/packages/router/tests/route.rs new file mode 100644 index 000000000..08a5b0aff --- /dev/null +++ b/packages/router/tests/route.rs @@ -0,0 +1,68 @@ +#![cfg(target_arch = "wasm32")] + +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_router::*; +use gloo_utils::document; +use serde::{Deserialize, Serialize}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn simple_test() { + fn main() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + dioxus_web::launch(APP); + } + + static APP: Component = |cx| { + cx.render(rsx! { + Router { + onchange: move |route| log::info!("route changed to {}", route), + Route { to: "/", Home {} } + Route { to: "blog" + Route { to: "/", BlogList {} } + Route { to: ":id", BlogPost {} } + } + } + }) + }; + + fn Home(cx: Scope) -> Element { + cx.render(rsx! { + div { + h1 { "Home" } + } + }) + } + + fn BlogList(cx: Scope) -> Element { + cx.render(rsx! { + div { + + } + }) + } + + fn BlogPost(cx: Scope) -> Element { + let id = use_route(&cx).segment::("id")?; + + cx.render(rsx! { + div { + id: "test1", + id + } + }) + } + + main(); + + let element = gloo_utils::document() + .get_element_by_id("test1") + .expect("No result found. Most likely, the application crashed") + .inner_html(); + assert!(element, ""); +} From 94c1da82644c6f4dfa824c75ba8c0b30c4bf3f9f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 15:00:36 -0500 Subject: [PATCH 068/103] chore: clean up documentation in diffing algorithm --- packages/core/src/diff.rs | 357 ++++++++++++++++++++++++-------------- 1 file changed, 222 insertions(+), 135 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index d40f49934..3f3629c10 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,6 +1,96 @@ #![warn(clippy::pedantic)] #![allow(clippy::cast_possible_truncation)] +//! This module contains the stateful [`DiffState`] and all methods to diff [`VNodes`], their properties, and their children. +//! +//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set +//! of mutations for the [`RealDom`] to apply. +//! +//! ## Notice: +//! +//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support +//! Components, Fragments, Suspense, [`SubTree`] memoization, incremental diffing, cancellation, [`NodeRefs`], pausing, priority +//! scheduling, and additional batching operations. +//! +//! ## Implementation Details: +//! +//! ### IDs for elements +//! -------------------- +//! All nodes are addressed by their IDs. The [`RealDom`] provides an imperative interface for making changes to these nodes. +//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose +//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element +//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive +//! garbage collection as the [`VirtualDOM`] replaces old nodes. +//! +//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing, +//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly +//! and brick the user's page. +//! +//! ### Fragment Support +//! -------------------- +//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments +//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty +//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`NodeFactory`] - it is +//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding +//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to +//! the platform. +//! +//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is +//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children. +//! +//! ### Suspense +//! ------------ +//! Dioxus implements Suspense slightly differently than React. In React, each fiber is manually progressed until it runs +//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once +//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before +//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on. +//! Due to the frequent calls to [`yield_now`] we can get the pure "fetch-as-you-render" behavior of React Fiber. +//! +//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to +//! DOM, but as a placeholder. +//! +//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes +//! and contents, without the need for the [`use_suspense`] hook. In the interim, this is the quickest way to get Suspense working. +//! +//! ## Subtree Memoization +//! ----------------------- +//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can +//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the +//! calls to "create" propagate this information upwards. Structures like the one below are entirely static: +//! ```rust, ignore +//! rsx!( div { class: "hello world", "this node is entirely static" } ) +//! ``` +//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to +//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time +//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as +//! a way of short-circuiting the most expensive checks. +//! +//! ## Bloom Filter and Heuristics +//! ------------------------------ +//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are +//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a +//! bump arena after first render, the number of hooks, and the number of nodes in the tree. +//! +//! ## Garbage Collection +//! --------------------- +//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage +//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then +//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient. +//! +//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy +//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes +//! for the client. As new nodes are created, old nodes will be over-written. +//! +//! ## Further Reading and Thoughts +//! ---------------------------- +//! There are more ways of increasing diff performance here that are currently not implemented. +//! - Strong memoization of subtrees. +//! - Guided diffing. +//! - Certain web-dom-specific optimizations. +//! +//! More info on how to improve this diffing algorithm: +//! - + use crate::innerlude::{ AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode, VPlaceholder, VText, @@ -45,35 +135,38 @@ impl<'b> DiffState<'b> { // these are *actual* elements, not wrappers around lists (Text(old), Text(new)) => { if std::ptr::eq(old, new) { - log::trace!("skipping node diff - text are the sames"); return; } - if let Some(root) = old.id.get() { - if old.text != new.text { - self.mutations.set_text(new.text, root.as_u64()); - } - self.scopes.update_node(new_node, root); + let root = old + .id + .get() + .expect("existing text nodes should have an ElementId"); - new.id.set(Some(root)); + if old.text != new.text { + self.mutations.set_text(new.text, root.as_u64()); } + self.scopes.update_node(new_node, root); + + new.id.set(Some(root)); } (Placeholder(old), Placeholder(new)) => { if std::ptr::eq(old, new) { - log::trace!("skipping node diff - placeholder are the sames"); return; } - if let Some(root) = old.id.get() { - self.scopes.update_node(new_node, root); - new.id.set(Some(root)); - } + let root = old + .id + .get() + .expect("existing placeholder nodes should have an ElementId"); + + self.scopes.update_node(new_node, root); + new.id.set(Some(root)); } (Element(old), Element(new)) => { if std::ptr::eq(old, new) { - log::trace!("skipping node diff - element are the sames"); return; } self.diff_element_nodes(old, new, old_node, new_node); @@ -82,7 +175,6 @@ impl<'b> DiffState<'b> { // These two sets are pointers to nodes but are not actually nodes themselves (Component(old), Component(new)) => { if std::ptr::eq(old, new) { - log::trace!("skipping node diff - placeholder are the sames"); return; } self.diff_component_nodes(old_node, new_node, *old, *new); @@ -90,7 +182,6 @@ impl<'b> DiffState<'b> { (Fragment(old), Fragment(new)) => { if std::ptr::eq(old, new) { - log::trace!("skipping node diff - fragment are the sames"); return; } self.diff_fragment_nodes(old, new); @@ -114,19 +205,17 @@ impl<'b> DiffState<'b> { } } - fn create_text_node(&mut self, vtext: &'b VText<'b>, node: &'b VNode<'b>) -> usize { + fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize { let real_id = self.scopes.reserve_node(node); - self.mutations.create_text_node(vtext.text, real_id); - vtext.id.set(Some(real_id)); - + text.id.set(Some(real_id)); + self.mutations.create_text_node(text.text, real_id); 1 } fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize { let real_id = self.scopes.reserve_node(node); - self.mutations.create_placeholder(real_id); anchor.id.set(Some(real_id)); - + self.mutations.create_placeholder(real_id); 1 } @@ -140,36 +229,35 @@ impl<'b> DiffState<'b> { id: dom_id, parent: parent_id, .. - } = element; + } = &element; - let parent = self.element_stack.last().unwrap(); - parent_id.set(Some(*parent)); + parent_id.set(self.element_stack.last().copied()); - // set the id of the element let real_id = self.scopes.reserve_node(node); + dom_id.set(Some(real_id)); self.element_stack.push(real_id); + { + self.mutations.create_element(tag_name, *namespace, real_id); - self.mutations.create_element(tag_name, *namespace, real_id); + let cur_scope_id = self + .current_scope() + .expect("diffing should always have a scope"); - if let Some(cur_scope_id) = self.current_scope() { - for listener in *listeners { + for listener in listeners.iter() { listener.mounted_node.set(Some(real_id)); self.mutations.new_event_listener(listener, cur_scope_id); } - } else { - log::trace!("create element called with no scope on the stack - this is an error for a live dom"); - } - for attr in *attributes { - self.mutations.set_attribute(attr, real_id.as_u64()); - } + for attr in attributes.iter() { + self.mutations.set_attribute(attr, real_id.as_u64()); + } - if !children.is_empty() { - self.create_and_append_children(children); + if !children.is_empty() { + self.create_and_append_children(children); + } } - self.element_stack.pop(); 1 @@ -182,25 +270,29 @@ impl<'b> DiffState<'b> { fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { let parent_idx = self.current_scope().unwrap(); - // the component might already exist - if it does, we need to reuse it - // this makes figure out when to drop the component more complicated - let new_idx = if let Some(idx) = vcomponent.scope.get() { - assert!(self.scopes.get_scope(idx).is_some()); - idx - } else { - // Insert a new scope into our component list - let props: Box = vcomponent.props.borrow_mut().take().unwrap(); - let props: Box = unsafe { std::mem::transmute(props) }; - let new_idx = self.scopes.new_with_key( - vcomponent.user_fc, - props, - Some(parent_idx), - self.element_stack.last().copied().unwrap(), - 0, - ); + // ensure this scope doesn't already exist if we're trying to create it + debug_assert!( + vcomponent + .scope + .get() + .and_then(|f| self.scopes.get_scope(f)) + .is_none(), + "component scope already exists" + ); - new_idx - }; + // Insert a new scope into our component list + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = unsafe { std::mem::transmute(props) }; + let new_idx = self.scopes.new_with_key( + vcomponent.user_fc, + props, + Some(parent_idx), + self.element_stack.last().copied().unwrap(), + 0, + ); + + // Actually initialize the caller's slot with the right address + vcomponent.scope.set(Some(new_idx)); log::trace!( "created component \"{}\", id: {:?} parent {:?} orig: {:?}", @@ -210,9 +302,6 @@ impl<'b> DiffState<'b> { vcomponent.originator ); - // Actually initialize the caller's slot with the right address - vcomponent.scope.set(Some(new_idx)); - // if vcomponent.can_memoize { // // todo: implement promotion logic. save us from boxing props that we don't need // } else { @@ -221,13 +310,15 @@ impl<'b> DiffState<'b> { self.enter_scope(new_idx); - // Run the scope for one iteration to initialize it - self.scopes.run_scope(new_idx); - self.mutations.mark_dirty_scope(new_idx); + let created = { + // Run the scope for one iteration to initialize it + self.scopes.run_scope(new_idx); + self.mutations.mark_dirty_scope(new_idx); - // Take the node that was just generated from running the component - let nextnode = self.scopes.fin_head(new_idx); - let created = self.create_node(nextnode); + // Take the node that was just generated from running the component + let nextnode = self.scopes.fin_head(new_idx); + self.create_node(nextnode) + }; self.leave_scope(); @@ -241,7 +332,10 @@ impl<'b> DiffState<'b> { old_node: &'b VNode<'b>, new_node: &'b VNode<'b>, ) { - let root = old.id.get().unwrap(); + let root = old + .id + .get() + .expect("existing element nodes should have an ElementId"); // If the element type is completely different, the element needs to be re-rendered completely // This is an optimization React makes due to how users structure their code @@ -330,7 +424,10 @@ impl<'b> DiffState<'b> { old: &'b VComponent<'b>, new: &'b VComponent<'b>, ) { - let scope_addr = old.scope.get().unwrap(); + let scope_addr = old + .scope + .get() + .expect("existing component nodes should have a scope"); if std::ptr::eq(old, new) { return; @@ -339,55 +436,55 @@ impl<'b> DiffState<'b> { // Make sure we're dealing with the same component (by function pointer) if old.user_fc == new.user_fc { self.enter_scope(scope_addr); + { + // Make sure the new component vnode is referencing the right scope id + new.scope.set(Some(scope_addr)); - // Make sure the new component vnode is referencing the right scope id - new.scope.set(Some(scope_addr)); + // make sure the component's caller function is up to date + let scope = self + .scopes + .get_scope(scope_addr) + .unwrap_or_else(|| panic!("could not find {:?}", scope_addr)); - // make sure the component's caller function is up to date - let scope = self - .scopes - .get_scope(scope_addr) - .unwrap_or_else(|| panic!("could not find {:?}", scope_addr)); - - // take the new props out regardless - // when memoizing, push to the existing scope if memoization happens - let new_props = new.props.borrow_mut().take().unwrap(); - - let should_run = { - if old.can_memoize { - let props_are_the_same = unsafe { - scope - .props - .borrow() - .as_ref() - .unwrap() - .memoize(new_props.as_ref()) - }; - !props_are_the_same || self.force_diff - } else { - true - } - }; - - if should_run { - let _old_props = scope + // take the new props out regardless + // when memoizing, push to the existing scope if memoization happens + let new_props = new .props - .replace(unsafe { std::mem::transmute(Some(new_props)) }); + .borrow_mut() + .take() + .expect("new component props should exist"); - // this should auto drop the previous props - self.scopes.run_scope(scope_addr); - self.mutations.mark_dirty_scope(scope_addr); + let should_diff = { + if old.can_memoize { + // safety: we trust the implementation of "memoize" + let props_are_the_same = unsafe { + let new_ref = new_props.as_ref(); + scope.props.borrow().as_ref().unwrap().memoize(new_ref) + }; + !props_are_the_same || self.force_diff + } else { + true + } + }; - self.diff_node( - self.scopes.wip_head(scope_addr), - self.scopes.fin_head(scope_addr), - ); - } else { - log::trace!("memoized"); - // memoization has taken place - drop(new_props); - }; + if should_diff { + let _old_props = scope + .props + .replace(unsafe { std::mem::transmute(Some(new_props)) }); + // this should auto drop the previous props + self.scopes.run_scope(scope_addr); + self.mutations.mark_dirty_scope(scope_addr); + + self.diff_node( + self.scopes.wip_head(scope_addr), + self.scopes.fin_head(scope_addr), + ); + } else { + // memoization has taken place + drop(new_props); + }; + } self.leave_scope(); } else { self.replace_node(old_node, new_node); @@ -410,10 +507,6 @@ impl<'b> DiffState<'b> { self.diff_children(old.children, new.children); } - // ============================================= - // Utilities for creating new diff instructions - // ============================================= - // Diff the given set of old and new children. // // The parent must be on top of the change list stack when this function is @@ -836,16 +929,17 @@ impl<'b> DiffState<'b> { let node = self.scopes.fin_head(scope_id); self.enter_scope(scope_id); + { + self.replace_inner(node, nodes_created); - self.replace_inner(node, nodes_created); + log::trace!("Replacing component x2 {:?}", old); - log::trace!("Replacing component x2 {:?}", old); + // we can only remove components if they are actively being diffed + if self.scope_stack.contains(&c.originator) { + log::trace!("Removing component {:?}", old); - // we can only remove components if they are actively being diffed - if self.scope_stack.contains(&c.originator) { - log::trace!("Removing component {:?}", old); - - self.scopes.try_remove(scope_id).unwrap(); + self.scopes.try_remove(scope_id).unwrap(); + } } self.leave_scope(); } @@ -891,22 +985,15 @@ impl<'b> DiffState<'b> { VNode::Component(c) => { self.enter_scope(c.scope.get().unwrap()); + { + let scope_id = c.scope.get().unwrap(); + let root = self.scopes.root_node(scope_id); + self.remove_nodes([root], gen_muts); - let scope_id = c.scope.get().unwrap(); - let root = self.scopes.root_node(scope_id); - self.remove_nodes([root], gen_muts); - - log::trace!( - "trying to remove scope {:?}, stack is {:#?}, originator is {:?}", - scope_id, - self.scope_stack, - c.originator - ); - - // we can only remove this node if the originator is actively - if self.scope_stack.contains(&c.originator) { - log::trace!("I am allowed to remove component because scope stack contains originator. Scope stack: {:#?} {:#?} {:#?}", self.scope_stack, c.originator, c.scope); - self.scopes.try_remove(scope_id).unwrap(); + // we can only remove this node if the originator is actively in our stackß + if self.scope_stack.contains(&c.originator) { + self.scopes.try_remove(scope_id).unwrap(); + } } self.leave_scope(); } From 8ad3f85872f77f3fc2a586dd18f980937a34a25d Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:13:18 +0100 Subject: [PATCH 069/103] fix ordering to match original one --- .vscode/settings.json | 1 + packages/router/Cargo.toml | 59 +++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ac2195ccd..221164c7c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,5 @@ "desktop", "router" ], + "editor.formatOnSave": false } diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 3bcb6a348..af701d2f2 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -1,51 +1,52 @@ [package] -description = "Dioxus VirtualDOM renderer for the web browser using websys" -documentation = "https://dioxuslabs.com" -edition = "2018" -homepage = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] -license = "MIT/Apache-2.0" name = "dioxus-router" -repository = "https://github.com/DioxusLabs/dioxus/" version = "0.1.1" +edition = "2018" +description = "Dioxus VirtualDOM renderer for the web browser using websys" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = {path = "../core", version = "^0.1.9", default-features = false} -dioxus-core-macro = {path = "../core-macro", version = "^0.1.7"} -dioxus-html = {path = "../html", version = "^0.1.6", default-features = false} +dioxus-core = { path = "../core", version = "^0.1.9", default-features = false } +dioxus-html = { path = "../html", version = "^0.1.6", default-features = false } +dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } serde = "1" -serde_urlencoded = "0.7" url = "2.2.2" +serde_urlencoded = "0.7" # for wasm -gloo = {version = "0.5", optional = true} -js-sys = {version = "0.3", optional = true} +web-sys = { version = "0.3", features = [ + "Attr", + "Document", + "History", + "HtmlBaseElement", + "Event", + "NamedNodeMap", + "Url", + "UrlSearchParams", + "Window", +], optional = true } +wasm-bindgen = { version = "0.2", optional = true } +js-sys = { version = "0.3", optional = true } +gloo = { version = "0.5", optional = true } log = "0.4.14" -wasm-bindgen = {version = "0.2", optional = true} -web-sys = {version = "0.3", features = [ - "Attr", - "Document", - "History", - "HtmlBaseElement", - "Event", - "NamedNodeMap", - "Url", - "UrlSearchParams", - "Window", -], optional = true} + [features] default = ["derive", "web"] -derive = [] +web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] desktop = [] mobile = [] -web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] +derive = [] [dev-dependencies] console_error_panic_hook = "0.1.7" -dioxus-web = {path = "../web"} +dioxus-web = { path = "../web" } log = "0.4.14" -wasm-bindgen-test = "0.3" wasm-logger = "0.2.0" +wasm-bindgen-test = "0.3" From 81ca1a001402a415458e79f2e2a8e6891d85c843 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:34:57 +0100 Subject: [PATCH 070/103] updated workflow to include make tests --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b941960b7..ce7ffecbd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,8 +35,8 @@ jobs: - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - uses: actions-rs/cargo@v1 with: - command: test - args: --features "desktop, ssr, router" + command: make + args: tests fmt: name: Rustfmt From ee67a041d2991fea2e867f14a4c8b4949421de6f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 15:38:48 -0500 Subject: [PATCH 071/103] feat: turn interpreter into crate --- Cargo.toml | 2 ++ packages/interpreter/Cargo.toml | 8 ++++++++ packages/{jsinterpreter => interpreter}/README.md | 0 packages/interpreter/build.rs | 6 ++++++ packages/{jsinterpreter => interpreter}/interpreter.js | 2 +- packages/{jsinterpreter => interpreter}/interpreter.ts | 6 +----- packages/interpreter/src/lib.rs | 1 + packages/{jsinterpreter => interpreter}/tsconfig.json | 0 8 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 packages/interpreter/Cargo.toml rename packages/{jsinterpreter => interpreter}/README.md (100%) create mode 100644 packages/interpreter/build.rs rename packages/{jsinterpreter => interpreter}/interpreter.js (99%) rename packages/{jsinterpreter => interpreter}/interpreter.ts (99%) create mode 100644 packages/interpreter/src/lib.rs rename packages/{jsinterpreter => interpreter}/tsconfig.json (100%) diff --git a/Cargo.toml b/Cargo.toml index 468518f5f..5e3a0a032 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true } dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true } dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true } +dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", optional = true } # dioxus-liveview = { path = "./packages/liveview", optional = true } [features] @@ -52,6 +53,7 @@ members = [ "packages/ssr", "packages/desktop", "packages/mobile", + "packages/interpreter", ] [dev-dependencies] diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml new file mode 100644 index 000000000..0934c08df --- /dev/null +++ b/packages/interpreter/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "dioxus-interpreter-js" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/packages/jsinterpreter/README.md b/packages/interpreter/README.md similarity index 100% rename from packages/jsinterpreter/README.md rename to packages/interpreter/README.md diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs new file mode 100644 index 000000000..1ef8b6548 --- /dev/null +++ b/packages/interpreter/build.rs @@ -0,0 +1,6 @@ +use std::process::Command; + +fn main() { + println!("cargo:rerun-if-changed=interpreter.ts"); + Command::new("tsc").spawn().unwrap(); +} diff --git a/packages/jsinterpreter/interpreter.js b/packages/interpreter/interpreter.js similarity index 99% rename from packages/jsinterpreter/interpreter.js rename to packages/interpreter/interpreter.js index ea50ec3d3..a70177252 100644 --- a/packages/jsinterpreter/interpreter.js +++ b/packages/interpreter/interpreter.js @@ -198,7 +198,7 @@ export class Interpreter { this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": - // this handler is only provided on desktop implementations since this + // this handler is only provided on desktop implementations since this // method is not used by the web implementation let handler = (event) => { let target = event.target; diff --git a/packages/jsinterpreter/interpreter.ts b/packages/interpreter/interpreter.ts similarity index 99% rename from packages/jsinterpreter/interpreter.ts rename to packages/interpreter/interpreter.ts index fdd8c671d..44d53cda8 100644 --- a/packages/jsinterpreter/interpreter.ts +++ b/packages/interpreter/interpreter.ts @@ -1,7 +1,3 @@ - - - - export function main() { let root = window.document.getElementById("main"); if (root != null) { @@ -245,7 +241,7 @@ export class Interpreter { case "NewEventListener": - // this handler is only provided on desktop implementations since this + // this handler is only provided on desktop implementations since this // method is not used by the web implementation let handler = (event: Event) => { let target = event.target as Element | null; diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs new file mode 100644 index 000000000..9c0229aa5 --- /dev/null +++ b/packages/interpreter/src/lib.rs @@ -0,0 +1 @@ +pub static INTERPRTER_JS: &str = include_str!("../interpreter.js"); diff --git a/packages/jsinterpreter/tsconfig.json b/packages/interpreter/tsconfig.json similarity index 100% rename from packages/jsinterpreter/tsconfig.json rename to packages/interpreter/tsconfig.json From 527434b9f93b9146153d9d94ee0b2f7eaca54bab Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 15:44:08 -0500 Subject: [PATCH 072/103] feat: make the interpreter as its own crate --- packages/desktop/Cargo.toml | 2 + packages/desktop/examples/async.rs | 8 +- packages/desktop/src/interpreter.js | 550 -------------------------- packages/desktop/src/lib.rs | 2 +- packages/web/Cargo.toml | 3 + packages/web/build.rs | 8 + packages/web/{src => }/interpreter.js | 2 +- packages/web/src/bindings.rs | 2 +- 8 files changed, 20 insertions(+), 557 deletions(-) delete mode 100644 packages/desktop/src/interpreter.js create mode 100644 packages/web/build.rs rename packages/web/{src => }/interpreter.js (99%) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 0d393e1ef..fa1bc88dc 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -31,6 +31,8 @@ dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" } webbrowser = "0.5.5" mime_guess = "2.0.3" +dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } + [features] default = ["tokio_runtime"] diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 056e7b591..064ef1960 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -10,14 +10,14 @@ fn main() { } fn app(cx: Scope) -> Element { - let count = use_state(&cx, || 0); + let (count, set_count) = use_state(&cx, || 0); use_future(&cx, || { - let count = count.for_async(); + let set_count = set_count.clone(); async move { loop { tokio::time::sleep(Duration::from_millis(1000)).await; - *count.modify() += 1; + set_count.modify(|f| f + 1) } } }); @@ -26,7 +26,7 @@ fn app(cx: Scope) -> Element { div { h1 { "High-Five counter: {count}" } button { - onclick: move |_| count.set(0), + onclick: move |_| set_count(0), "Click me!" } } diff --git a/packages/desktop/src/interpreter.js b/packages/desktop/src/interpreter.js deleted file mode 100644 index 503ea7247..000000000 --- a/packages/desktop/src/interpreter.js +++ /dev/null @@ -1,550 +0,0 @@ -export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} -export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); - } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute( - `dioxus-prevent-default` - ); - - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - console.log("click", event); - console.log("clickeded", event.target); - console.log("clickeded", event.target.tagName); - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - - shouldPreventDefault = target.getAttribute( - `dioxus-prevent-default` - ); - - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { - charCode, - key, - altKey, - ctrlKey, - metaKey, - keyCode, - shiftKey, - location, - repeat, - which, - } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } else { - value = target.value ?? target.textContent; - } - return { - value: value, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - pointerId, - width, - height, - pressure, - tangentialPressure, - tiltX, - tiltY, - twist, - pointerType, - isPrimary, - } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 22acc4f85..9181d04dc 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -224,7 +224,7 @@ pub fn launch_with_props( } else if trimmed == "index.js" { wry::http::ResponseBuilder::new() .mimetype("text/javascript") - .body(include_bytes!("./interpreter.js").to_vec()) + .body(dioxus_interpreter_js::INTERPRTER_JS.as_bytes().to_vec()) } else { // Read the file content from file path use std::fs::read; diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index a14d6cb0a..9ac508a8c 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -29,6 +29,9 @@ gloo-timers = { version = "0.2.1", features = ["futures"] } futures-util = "0.3.15" smallstr = "0.2.0" +[build-dependencies] +dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } + [dependencies.web-sys] version = "0.3.51" features = [ diff --git a/packages/web/build.rs b/packages/web/build.rs new file mode 100644 index 000000000..bdccab341 --- /dev/null +++ b/packages/web/build.rs @@ -0,0 +1,8 @@ +use std::{fs::File, io::Write}; + +fn main() { + // write the interpreter code to a local file + let mut file = File::create("interpreter.js").unwrap(); + file.write_all(dioxus_interpreter_js::INTERPRTER_JS.as_bytes()) + .unwrap(); +} diff --git a/packages/web/src/interpreter.js b/packages/web/interpreter.js similarity index 99% rename from packages/web/src/interpreter.js rename to packages/web/interpreter.js index ea50ec3d3..a70177252 100644 --- a/packages/web/src/interpreter.js +++ b/packages/web/interpreter.js @@ -198,7 +198,7 @@ export class Interpreter { this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": - // this handler is only provided on desktop implementations since this + // this handler is only provided on desktop implementations since this // method is not used by the web implementation let handler = (event) => { let target = event.target; diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs index fa96e3230..55506b73c 100644 --- a/packages/web/src/bindings.rs +++ b/packages/web/src/bindings.rs @@ -2,7 +2,7 @@ use js_sys::Function; use wasm_bindgen::prelude::*; use web_sys::{Element, Node}; -#[wasm_bindgen(module = "/src/interpreter.js")] +#[wasm_bindgen(module = "/interpreter.js")] extern "C" { pub type Interpreter; From 3f97cbdaa5347bbcb2e32f68366b32245edd7839 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:44:21 +0100 Subject: [PATCH 073/103] format on save: toml excluded --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 221164c7c..44bcb08e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "desktop", "router" ], - "editor.formatOnSave": false + "editor.formatOnSave": true, + "[toml]": { + "editor.formatOnSave": false + } } From 5b69f9b68611d109cbc4dcc4ead2cdf2c82c01c4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 15:45:03 -0500 Subject: [PATCH 074/103] release: make interpreter crate publishable --- packages/interpreter/Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index 0934c08df..fe150e1f9 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -2,6 +2,14 @@ name = "dioxus-interpreter-js" version = "0.0.0" edition = "2018" +authors = ["Jonathan Kelley"] +description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://docs.rs/dioxus" +keywords = ["dom", "ui", "gui", "react", "wasm"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8568678f501c84d679c2fdf44be2a1423db2f1d2 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:50:55 +0100 Subject: [PATCH 075/103] install cargo make --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce7ffecbd..9a6a7e143 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,6 +33,11 @@ jobs: - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - name: Install cargo-make + uses: actions-rs/cargo@v1 + with: + command: install + args: --debug cargo-make - uses: actions-rs/cargo@v1 with: command: make From 71656adc897dc1e3fb50010e10b3488059d12dfe Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 15:54:32 -0500 Subject: [PATCH 076/103] publish: generate our bindings.rs file from an inlinejs snippet --- packages/interpreter/build.rs | 11 +- packages/web/build.rs | 72 ++++- packages/web/src/bindings.rs | 502 +++++++++++++++++++++++++++++++++- 3 files changed, 579 insertions(+), 6 deletions(-) diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs index 1ef8b6548..f6047e356 100644 --- a/packages/interpreter/build.rs +++ b/packages/interpreter/build.rs @@ -2,5 +2,14 @@ use std::process::Command; fn main() { println!("cargo:rerun-if-changed=interpreter.ts"); - Command::new("tsc").spawn().unwrap(); + match Command::new("tsc").spawn() { + Ok(_) => println!("Was spawned :)"), + Err(e) => { + if let std::io::ErrorKind::NotFound = e.kind() { + println!("`tsc` was not found! Not going to generate new interpreter") + } else { + println!("Some strange error occurred :("); + } + } + } } diff --git a/packages/web/build.rs b/packages/web/build.rs index bdccab341..85c8087c6 100644 --- a/packages/web/build.rs +++ b/packages/web/build.rs @@ -1,8 +1,72 @@ use std::{fs::File, io::Write}; fn main() { - // write the interpreter code to a local file - let mut file = File::create("interpreter.js").unwrap(); - file.write_all(dioxus_interpreter_js::INTERPRTER_JS.as_bytes()) - .unwrap(); + let src = format!( + r###" +use js_sys::Function; +use wasm_bindgen::prelude::*; +use web_sys::{{Element, Node}}; + +#[wasm_bindgen(inline_js = r##"{}"##)] +extern "C" {{ + pub type Interpreter; + + #[wasm_bindgen(constructor)] + pub fn new(arg: Element) -> Interpreter; + + #[wasm_bindgen(method)] + pub fn set_node(this: &Interpreter, id: usize, node: Node); + + #[wasm_bindgen(method)] + pub fn PushRoot(this: &Interpreter, root: u64); + + #[wasm_bindgen(method)] + pub fn AppendChildren(this: &Interpreter, many: u32); + + #[wasm_bindgen(method)] + pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32); + + #[wasm_bindgen(method)] + pub fn InsertAfter(this: &Interpreter, root: u64, n: u32); + + #[wasm_bindgen(method)] + pub fn InsertBefore(this: &Interpreter, root: u64, n: u32); + + #[wasm_bindgen(method)] + pub fn Remove(this: &Interpreter, root: u64); + + #[wasm_bindgen(method)] + pub fn CreateTextNode(this: &Interpreter, text: &str, root: u64); + + #[wasm_bindgen(method)] + pub fn CreateElement(this: &Interpreter, tag: &str, root: u64); + + #[wasm_bindgen(method)] + pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str); + + #[wasm_bindgen(method)] + pub fn CreatePlaceholder(this: &Interpreter, root: u64); + + #[wasm_bindgen(method)] + pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function); + + #[wasm_bindgen(method)] + pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str); + + #[wasm_bindgen(method)] + pub fn SetText(this: &Interpreter, root: u64, text: &str); + + #[wasm_bindgen(method)] + pub fn SetAttribute(this: &Interpreter, root: u64, field: &str, value: &str, ns: Option<&str>); + + #[wasm_bindgen(method)] + pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); +}} +"###, + dioxus_interpreter_js::INTERPRTER_JS + ); + + // write the bindings to a local file + let mut file = File::create("src/bindings.rs").unwrap(); + file.write_all(src.as_bytes()).unwrap(); } diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs index 55506b73c..5369708f1 100644 --- a/packages/web/src/bindings.rs +++ b/packages/web/src/bindings.rs @@ -1,8 +1,508 @@ + use js_sys::Function; use wasm_bindgen::prelude::*; use web_sys::{Element, Node}; -#[wasm_bindgen(module = "/interpreter.js")] +#[wasm_bindgen(inline_js = r##"export function main() { + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.rpc.call("initialize"); + } +} +export class Interpreter { + root; + stack; + listeners; + handlers; + lastNodeWasText; + nodes; + constructor(root) { + this.root = root; + this.stack = [root]; + this.listeners = {}; + this.handlers = {}; + this.lastNodeWasText = false; + this.nodes = [root]; + } + top() { + return this.stack[this.stack.length - 1]; + } + pop() { + return this.stack.pop(); + } + PushRoot(root) { + const node = this.nodes[root]; + this.stack.push(node); + } + AppendChildren(many) { + let root = this.stack[this.stack.length - (1 + many)]; + let to_add = this.stack.splice(this.stack.length - many); + for (let i = 0; i < many; i++) { + root.appendChild(to_add[i]); + } + } + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; + let els = this.stack.splice(this.stack.length - m); + root.replaceWith(...els); + } + InsertAfter(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.after(...new_nodes); + } + InsertBefore(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.before(...new_nodes); + } + Remove(root) { + let node = this.nodes[root]; + if (node !== undefined) { + node.remove(); + } + } + CreateTextNode(text, root) { + // todo: make it so the types are okay + const node = document.createTextNode(text); + this.nodes[root] = node; + this.stack.push(node); + } + CreateElement(tag, root) { + const el = document.createElement(tag); + // el.setAttribute("data-dioxus-id", `${root}`); + this.nodes[root] = el; + this.stack.push(el); + } + CreateElementNs(tag, root, ns) { + let el = document.createElementNS(ns, tag); + this.stack.push(el); + this.nodes[root] = el; + } + CreatePlaceholder(root) { + let el = document.createElement("pre"); + el.hidden = true; + this.stack.push(el); + this.nodes[root] = el; + } + NewEventListener(event_name, root, handler) { + const element = this.nodes[root]; + element.setAttribute("data-dioxus-id", `${root}`); + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } + else { + this.listeners[event_name]++; + } + } + RemoveEventListener(root, event_name) { + const element = this.nodes[root]; + element.removeAttribute(`data-dioxus-id`); + this.listeners[event_name]--; + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; + } + } + SetText(root, text) { + this.nodes[root].textContent = text; + } + SetAttribute(root, field, value, ns) { + const name = field; + const node = this.nodes[root]; + if (ns == "style") { + // @ts-ignore + node.style[name] = value; + } + else if (ns != null || ns != undefined) { + node.setAttributeNS(ns, name, value); + } + else { + switch (name) { + case "value": + if (value != node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value == "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } + else { + node.setAttribute(name, value); + } + } + } + } + RemoveAttribute(root, name) { + const node = this.nodes[root]; + node.removeAttribute(name); + if (name === "value") { + node.value = ""; + } + if (name === "checked") { + node.checked = false; + } + if (name === "selected") { + node.selected = false; + } + } + handleEdits(edits) { + this.stack.push(this.root); + for (let edit of edits) { + this.handleEdit(edit); + } + } + handleEdit(edit) { + switch (edit.type) { + case "PushRoot": + this.PushRoot(edit.root); + break; + case "AppendChildren": + this.AppendChildren(edit.many); + break; + case "ReplaceWith": + this.ReplaceWith(edit.root, edit.m); + break; + case "InsertAfter": + this.InsertAfter(edit.root, edit.n); + break; + case "InsertBefore": + this.InsertBefore(edit.root, edit.n); + break; + case "Remove": + this.Remove(edit.root); + break; + case "CreateTextNode": + this.CreateTextNode(edit.text, edit.root); + break; + case "CreateElement": + this.CreateElement(edit.tag, edit.root); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.root, edit.ns); + break; + case "CreatePlaceholder": + this.CreatePlaceholder(edit.root); + break; + case "RemoveEventListener": + this.RemoveEventListener(edit.root, edit.event_name); + break; + case "NewEventListener": + // this handler is only provided on desktop implementations since this + // method is not used by the web implementation + let handler = (event) => { + let target = event.target; + if (target != null) { + let realId = target.getAttribute(`data-dioxus-id`); + // walk the tree to find the real element + while (realId == null && target.parentElement != null) { + target = target.parentElement; + realId = target.getAttribute(`data-dioxus-id`); + } + const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + let contents = serialize_event(event); + if (shouldPreventDefault === `on${event.type}`) { + event.preventDefault(); + } + if (event.type == "submit") { + event.preventDefault(); + } + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined && realId != null) { + window.rpc.call("browser_open", { + mounted_dom_id: parseInt(realId), + href + }); + } + } + } + } + if (realId == null) { + return; + } + window.rpc.call("user_event", { + event: edit.event_name, + mounted_dom_id: parseInt(realId), + contents: contents, + }); + } + }; + this.NewEventListener(edit.event_name, edit.root, handler); + break; + case "SetText": + this.SetText(edit.root, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.root, edit.name); + break; + } + } +} +function serialize_event(event) { + switch (event.type) { + case "copy": + case "cut": + case "past": { + return {}; + } + case "compositionend": + case "compositionstart": + case "compositionupdate": { + let { data } = event; + return { + data, + }; + } + case "keydown": + case "keypress": + case "keyup": { + let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; + return { + char_code: charCode, + key: key, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + key_code: keyCode, + shift_key: shiftKey, + location: location, + repeat: repeat, + which: which, + locale: "locale", + }; + } + case "focus": + case "blur": { + return {}; + } + case "change": { + let target = event.target; + let value; + if (target.type === "checkbox" || target.type === "radio") { + value = target.checked ? "true" : "false"; + } + else { + value = target.value ?? target.textContent; + } + return { + value: value, + }; + } + case "input": + case "invalid": + case "reset": + case "submit": { + let target = event.target; + let value = target.value ?? target.textContent; + if (target.type == "checkbox") { + value = target.checked ? "true" : "false"; + } + return { + value: value, + }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "drag": + case "dragend": + case "dragenter": + case "dragexit": + case "dragleave": + case "dragover": + case "dragstart": + case "drop": + case "mousedown": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "mouseup": { + const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; + } + case "pointerdown": + case "pointermove": + case "pointerup": + case "pointercancel": + case "gotpointercapture": + case "lostpointercapture": + case "pointerenter": + case "pointerleave": + case "pointerover": + case "pointerout": { + const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + pointer_id: pointerId, + width: width, + height: height, + pressure: pressure, + tangential_pressure: tangentialPressure, + tilt_x: tiltX, + tilt_y: tiltY, + twist: twist, + pointer_type: pointerType, + is_primary: isPrimary, + }; + } + case "select": { + return {}; + } + case "touchcancel": + case "touchend": + case "touchmove": + case "touchstart": { + const { altKey, ctrlKey, metaKey, shiftKey, } = event; + return { + // changed_touches: event.changedTouches, + // target_touches: event.targetTouches, + // touches: event.touches, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, + }; + } + case "scroll": { + return {}; + } + case "wheel": { + const { deltaX, deltaY, deltaZ, deltaMode, } = event; + return { + delta_x: deltaX, + delta_y: deltaY, + delta_z: deltaZ, + delta_mode: deltaMode, + }; + } + case "animationstart": + case "animationend": + case "animationiteration": { + const { animationName, elapsedTime, pseudoElement, } = event; + return { + animation_name: animationName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "transitionend": { + const { propertyName, elapsedTime, pseudoElement, } = event; + return { + property_name: propertyName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "abort": + case "canplay": + case "canplaythrough": + case "durationchange": + case "emptied": + case "encrypted": + case "ended": + case "error": + case "loadeddata": + case "loadedmetadata": + case "loadstart": + case "pause": + case "play": + case "playing": + case "progress": + case "ratechange": + case "seeked": + case "seeking": + case "stalled": + case "suspend": + case "timeupdate": + case "volumechange": + case "waiting": { + return {}; + } + case "toggle": { + return {}; + } + default: { + return {}; + } + } +} +const bool_attrs = { + allowfullscreen: true, + allowpaymentrequest: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, + truespeed: true, +}; +"##)] extern "C" { pub type Interpreter; From 86bac80040f500c2581fee536eff3a04666bc18c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 15:57:07 -0500 Subject: [PATCH 077/103] fmt: make bindings obey formatting --- packages/web/build.rs | 8 ++++++-- packages/web/src/bindings.rs | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/web/build.rs b/packages/web/build.rs index 85c8087c6..3c9baa7c8 100644 --- a/packages/web/build.rs +++ b/packages/web/build.rs @@ -2,11 +2,15 @@ use std::{fs::File, io::Write}; fn main() { let src = format!( - r###" -use js_sys::Function; + r###"use js_sys::Function; use wasm_bindgen::prelude::*; use web_sys::{{Element, Node}}; +/* +This is an autogenerated file from build.rs. +Do not edit this file directly. +*/ + #[wasm_bindgen(inline_js = r##"{}"##)] extern "C" {{ pub type Interpreter; diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs index 5369708f1..4b25260a3 100644 --- a/packages/web/src/bindings.rs +++ b/packages/web/src/bindings.rs @@ -1,8 +1,12 @@ - use js_sys::Function; use wasm_bindgen::prelude::*; use web_sys::{Element, Node}; +/* +This is an autogenerated file from build.rs. +Do not edit this file directly. +*/ + #[wasm_bindgen(inline_js = r##"export function main() { let root = window.document.getElementById("main"); if (root != null) { From 12cfd79d51a54e2da6aecd437559a563a7233b8b Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 22:04:21 +0100 Subject: [PATCH 078/103] commented out failing example --- packages/desktop/examples/async.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 056e7b591..47b17b1b4 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -1,4 +1,4 @@ -use dioxus::prelude::*; +/* use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_hooks::*; @@ -32,3 +32,4 @@ fn app(cx: Scope) -> Element { } }) } + */ From b4923b2b81108630d37a239f31eb5cda6d4626e2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 1 Feb 2022 16:07:50 -0500 Subject: [PATCH 079/103] ci: move gen folder out of src --- packages/interpreter/build.rs | 1 + packages/interpreter/{ => gen}/interpreter.js | 0 packages/interpreter/{ => src}/interpreter.ts | 0 packages/interpreter/src/lib.rs | 2 +- packages/interpreter/tsconfig.json | 2 ++ 5 files changed, 4 insertions(+), 1 deletion(-) rename packages/interpreter/{ => gen}/interpreter.js (100%) rename packages/interpreter/{ => src}/interpreter.ts (100%) diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs index f6047e356..610e3c3c3 100644 --- a/packages/interpreter/build.rs +++ b/packages/interpreter/build.rs @@ -2,6 +2,7 @@ use std::process::Command; fn main() { println!("cargo:rerun-if-changed=interpreter.ts"); + match Command::new("tsc").spawn() { Ok(_) => println!("Was spawned :)"), Err(e) => { diff --git a/packages/interpreter/interpreter.js b/packages/interpreter/gen/interpreter.js similarity index 100% rename from packages/interpreter/interpreter.js rename to packages/interpreter/gen/interpreter.js diff --git a/packages/interpreter/interpreter.ts b/packages/interpreter/src/interpreter.ts similarity index 100% rename from packages/interpreter/interpreter.ts rename to packages/interpreter/src/interpreter.ts diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 9c0229aa5..7d680ae25 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1 +1 @@ -pub static INTERPRTER_JS: &str = include_str!("../interpreter.js"); +pub static INTERPRTER_JS: &str = include_str!("../gen/interpreter.js"); diff --git a/packages/interpreter/tsconfig.json b/packages/interpreter/tsconfig.json index f63034044..1c1ed1ef9 100644 --- a/packages/interpreter/tsconfig.json +++ b/packages/interpreter/tsconfig.json @@ -8,6 +8,8 @@ "es6", "dom" ], + "rootDir": "src", "strict": true, + "outDir": "gen", } } From 022d6d7850dba2e6f68edab3a89e383c8181321c Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 22:37:34 +0100 Subject: [PATCH 080/103] exclude dioxus-mobile in tests (future feature) --- Makefile.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.toml b/Makefile.toml index 23eeacea2..95abec9de 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -37,6 +37,6 @@ private = true workspace = true [tasks.test] -args = ["test", "--all-targets"] +args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-mobile"] command = "cargo" private = true From 84959a78262f20a09e39bc4c79fffc383dd29877 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 01:08:59 -0500 Subject: [PATCH 081/103] feat: it works everywhere --- packages/interpreter/README.md | 1 - packages/interpreter/gen/interpreter.js | 28 +- packages/interpreter/src/interpreter.ts | 29 +- packages/web/interpreter.js | 499 ------------------------ packages/web/src/bindings.rs | 28 +- 5 files changed, 40 insertions(+), 545 deletions(-) delete mode 100644 packages/web/interpreter.js diff --git a/packages/interpreter/README.md b/packages/interpreter/README.md index bab57ac16..767fe31c0 100644 --- a/packages/interpreter/README.md +++ b/packages/interpreter/README.md @@ -4,5 +4,4 @@ After diffing old and new trees, the Dioxus VirtualDom produces patches that are In renderers with support for JavaScript, we use the interpreter from this repository - written in TypeScript - to patch the Dom. This lets us circumvent any overhead on the Rust <-> Dom boundary and keep consistency in our interpreter implementation in web/webview targets. - For now - both Dioxus Web and Dioxus Desktop (webview) use the same interpreter code with tweaks. diff --git a/packages/interpreter/gen/interpreter.js b/packages/interpreter/gen/interpreter.js index a70177252..0d89efa85 100644 --- a/packages/interpreter/gen/interpreter.js +++ b/packages/interpreter/gen/interpreter.js @@ -204,12 +204,24 @@ export class Interpreter { let target = event.target; if (target != null) { let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined) { + window.rpc.call("browser_open", { href }); + } + } + } + } // walk the tree to find the real element while (realId == null && target.parentElement != null) { target = target.parentElement; realId = target.getAttribute(`data-dioxus-id`); } - const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); let contents = serialize_event(event); if (shouldPreventDefault === `on${event.type}`) { event.preventDefault(); @@ -217,20 +229,6 @@ export class Interpreter { if (event.type == "submit") { event.preventDefault(); } - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined && realId != null) { - window.rpc.call("browser_open", { - mounted_dom_id: parseInt(realId), - href - }); - } - } - } - } if (realId == null) { return; } diff --git a/packages/interpreter/src/interpreter.ts b/packages/interpreter/src/interpreter.ts index 44d53cda8..f150410f4 100644 --- a/packages/interpreter/src/interpreter.ts +++ b/packages/interpreter/src/interpreter.ts @@ -248,6 +248,19 @@ export class Interpreter { if (target != null) { let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href") + if (href !== "" && href !== null && href !== undefined) { + window.rpc.call("browser_open", { href }); + } + } + } + } // walk the tree to find the real element while (realId == null && target.parentElement != null) { @@ -255,7 +268,7 @@ export class Interpreter { realId = target.getAttribute(`data-dioxus-id`); } - const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); let contents = serialize_event(event); @@ -267,20 +280,6 @@ export class Interpreter { event.preventDefault(); } - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href") - if (href !== "" && href !== null && href !== undefined && realId != null) { - window.rpc.call("browser_open", { - mounted_dom_id: parseInt(realId), - href - }); - } - } - } - } if (realId == null) { return; diff --git a/packages/web/interpreter.js b/packages/web/interpreter.js deleted file mode 100644 index a70177252..000000000 --- a/packages/web/interpreter.js +++ /dev/null @@ -1,499 +0,0 @@ -export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} -export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); - } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined && realId != null) { - window.rpc.call("browser_open", { - mounted_dom_id: parseInt(realId), - href - }); - } - } - } - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } - else { - value = target.value ?? target.textContent; - } - return { - value: value, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs index 4b25260a3..4834146af 100644 --- a/packages/web/src/bindings.rs +++ b/packages/web/src/bindings.rs @@ -213,12 +213,24 @@ export class Interpreter { let target = event.target; if (target != null) { let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + if (event.type == "click") { + event.preventDefault(); + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined) { + window.rpc.call("browser_open", { href }); + } + } + } + } // walk the tree to find the real element while (realId == null && target.parentElement != null) { target = target.parentElement; realId = target.getAttribute(`data-dioxus-id`); } - const shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); + shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); let contents = serialize_event(event); if (shouldPreventDefault === `on${event.type}`) { event.preventDefault(); @@ -226,20 +238,6 @@ export class Interpreter { if (event.type == "submit") { event.preventDefault(); } - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined && realId != null) { - window.rpc.call("browser_open", { - mounted_dom_id: parseInt(realId), - href - }); - } - } - } - } if (realId == null) { return; } From b2b2dae7e611cb9eb0a0f5864759921151919655 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 11:33:02 -0500 Subject: [PATCH 082/103] fix: diffing allows component reuse --- packages/core/src/diff.rs | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3f3629c10..48163ccf7 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -270,26 +270,23 @@ impl<'b> DiffState<'b> { fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { let parent_idx = self.current_scope().unwrap(); - // ensure this scope doesn't already exist if we're trying to create it - debug_assert!( - vcomponent - .scope - .get() - .and_then(|f| self.scopes.get_scope(f)) - .is_none(), - "component scope already exists" - ); - - // Insert a new scope into our component list - let props: Box = vcomponent.props.borrow_mut().take().unwrap(); - let props: Box = unsafe { std::mem::transmute(props) }; - let new_idx = self.scopes.new_with_key( - vcomponent.user_fc, - props, - Some(parent_idx), - self.element_stack.last().copied().unwrap(), - 0, - ); + // the component might already exist - if it does, we need to reuse it + // this makes figure out when to drop the component more complicated + let new_idx = if let Some(idx) = vcomponent.scope.get() { + assert!(self.scopes.get_scope(idx).is_some()); + idx + } else { + // Insert a new scope into our component list + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = unsafe { std::mem::transmute(props) }; + self.scopes.new_with_key( + vcomponent.user_fc, + props, + Some(parent_idx), + self.element_stack.last().copied().unwrap(), + 0, + ) + }; // Actually initialize the caller's slot with the right address vcomponent.scope.set(Some(new_idx)); From 24d78682ad7dfe72297844929b7d79a321e73d1e Mon Sep 17 00:00:00 2001 From: t1m0t Date: Wed, 2 Feb 2022 21:35:57 +0100 Subject: [PATCH 083/103] add docker folder for handling local tests --- docker | 1 + 1 file changed, 1 insertion(+) create mode 160000 docker diff --git a/docker b/docker new file mode 160000 index 000000000..022d6d785 --- /dev/null +++ b/docker @@ -0,0 +1 @@ +Subproject commit 022d6d7850dba2e6f68edab3a89e383c8181321c From f1865faef790754ca3192a5a9a387e2f225e72a0 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 00:11:38 +0100 Subject: [PATCH 084/103] setup done, local tests work, check workflow --- .github/workflows/main.yml | 2 +- packages/mobile/Cargo.toml | 5 +++++ packages/mobile/Makefile.toml | 7 +++++++ packages/router/Cargo.toml | 8 ++++++++ packages/router/Makefile.toml | 6 +++--- packages/router/tests/route.rs | 9 ++------- packages/router/webdriver.json | 10 ++++++++++ 7 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 packages/mobile/Makefile.toml create mode 100644 packages/router/webdriver.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a6a7e143..d649d47ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev firefox-csr - name: Install cargo-make uses: actions-rs/cargo@v1 with: diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 9da440343..50647a693 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -13,3 +13,8 @@ license = "MIT/Apache-2.0" [dependencies] dioxus-desktop = { path = "../desktop", version = "^0.1.6" } + +[lib] +doctest = false +# tests suspended until package ready +test = false \ No newline at end of file diff --git a/packages/mobile/Makefile.toml b/packages/mobile/Makefile.toml new file mode 100644 index 000000000..90f08a170 --- /dev/null +++ b/packages/mobile/Makefile.toml @@ -0,0 +1,7 @@ +[tasks.test] +command = "cargo" +args = [ + "test", + "--no-run", +] + diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index af701d2f2..b35760889 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -43,6 +43,7 @@ web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] desktop = [] mobile = [] derive = [] +wasm_test = [] [dev-dependencies] console_error_panic_hook = "0.1.7" @@ -50,3 +51,10 @@ dioxus-web = { path = "../web" } log = "0.4.14" wasm-logger = "0.2.0" wasm-bindgen-test = "0.3" +gloo-utils = "0.1.2" + +[dev-dependencies.web-sys] +version = "0.3" +features = [ + "Document", +] \ No newline at end of file diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml index 27a6c9244..989b1cc7b 100644 --- a/packages/router/Makefile.toml +++ b/packages/router/Makefile.toml @@ -1,10 +1,10 @@ [tasks.test] +extend = "core::wasm-pack-base" +command = "wasm-pack" args = [ "test", "@@split(DIOXUS_TEST_FLAGS, )", "--", "--features", "${DIOXUS_TEST_FEATURES}", -] -command = "wasm-pack" -extend = "core::wasm-pack-base" +] \ No newline at end of file diff --git a/packages/router/tests/route.rs b/packages/router/tests/route.rs index 08a5b0aff..89b7a64fa 100644 --- a/packages/router/tests/route.rs +++ b/packages/router/tests/route.rs @@ -52,17 +52,12 @@ fn simple_test() { cx.render(rsx! { div { - id: "test1", - id + } }) } main(); - let element = gloo_utils::document() - .get_element_by_id("test1") - .expect("No result found. Most likely, the application crashed") - .inner_html(); - assert!(element, ""); + let element = gloo_utils::document(); } diff --git a/packages/router/webdriver.json b/packages/router/webdriver.json new file mode 100644 index 000000000..5f6013c9d --- /dev/null +++ b/packages/router/webdriver.json @@ -0,0 +1,10 @@ +{ + "moz:firefoxOptions": { + "binary": "/usr/bin/firefox", + "prefs": { + "media.navigator.streams.fake": true, + "media.navigator.permission.disabled": true + }, + "args": [] + } + } \ No newline at end of file From 3b55669373a7cc23eda53b996d574a1f4aac32b1 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 00:16:56 +0100 Subject: [PATCH 085/103] try fix CI --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d649d47ab..4ab05df1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,13 +32,14 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev firefox-csr + - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - name: Install cargo-make uses: actions-rs/cargo@v1 with: command: install args: --debug cargo-make - uses: actions-rs/cargo@v1 + - uses: browser-actions/setup-firefox@latest with: command: make args: tests From bc43069884d74fe01f77cdfbc2b492db5f9a328e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 19:04:37 -0500 Subject: [PATCH 086/103] fix: allow scopes and nodes to be missing --- packages/core/src/diff.rs | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 48163ccf7..2c8a66ca8 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -329,10 +329,11 @@ impl<'b> DiffState<'b> { old_node: &'b VNode<'b>, new_node: &'b VNode<'b>, ) { - let root = old - .id - .get() - .expect("existing element nodes should have an ElementId"); + // if the node is comming back not assigned, that means it was borrowed but removed + let root = match old.id.get() { + Some(id) => id, + None => self.scopes.reserve_node(new_node), + }; // If the element type is completely different, the element needs to be re-rendered completely // This is an optimization React makes due to how users structure their code @@ -931,12 +932,18 @@ impl<'b> DiffState<'b> { log::trace!("Replacing component x2 {:?}", old); - // we can only remove components if they are actively being diffed - if self.scope_stack.contains(&c.originator) { - log::trace!("Removing component {:?}", old); + let scope = self.scopes.get_scope(scope_id).unwrap(); + c.scope.set(None); + let props = scope.props.take().unwrap(); + c.props.borrow_mut().replace(props); + self.scopes.try_remove(scope_id).unwrap(); - self.scopes.try_remove(scope_id).unwrap(); - } + // // we can only remove components if they are actively being diffed + // if self.scope_stack.contains(&c.originator) { + // log::trace!("Removing component {:?}", old); + + // self.scopes.try_remove(scope_id).unwrap(); + // } } self.leave_scope(); } @@ -950,6 +957,7 @@ impl<'b> DiffState<'b> { // this check exists because our null node will be removed but does not have an ID if let Some(id) = t.id.get() { self.scopes.collect_garbage(id); + t.id.set(None); if gen_muts { self.mutations.remove(id.as_u64()); @@ -959,6 +967,7 @@ impl<'b> DiffState<'b> { VNode::Placeholder(a) => { let id = a.id.get().unwrap(); self.scopes.collect_garbage(id); + a.id.set(None); if gen_muts { self.mutations.remove(id.as_u64()); @@ -972,6 +981,7 @@ impl<'b> DiffState<'b> { } self.scopes.collect_garbage(id); + e.id.set(None); self.remove_nodes(e.children, false); } @@ -987,10 +997,12 @@ impl<'b> DiffState<'b> { let root = self.scopes.root_node(scope_id); self.remove_nodes([root], gen_muts); - // we can only remove this node if the originator is actively in our stackß - if self.scope_stack.contains(&c.originator) { - self.scopes.try_remove(scope_id).unwrap(); - } + let scope = self.scopes.get_scope(scope_id).unwrap(); + c.scope.set(None); + + let props = scope.props.take().unwrap(); + c.props.borrow_mut().replace(props); + self.scopes.try_remove(scope_id).unwrap(); } self.leave_scope(); } From f7b3228ab2a3bc199834b478001d37022ffd05c4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 20:04:55 -0500 Subject: [PATCH 087/103] fix: enable resume node for text and placeholder --- packages/core/src/diff.rs | 108 ++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2c8a66ca8..06d8346b4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -119,11 +119,15 @@ impl<'b> DiffState<'b> { pub fn diff_scope(&mut self, scopeid: ScopeId) { let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); - self.scope_stack.push(scopeid); let scope = self.scopes.get_scope(scopeid).unwrap(); - self.element_stack.push(scope.container); - self.diff_node(old, new); + self.scope_stack.push(scopeid); + self.element_stack.push(scope.container); + { + self.diff_node(old, new); + } + self.element_stack.pop(); + self.scope_stack.pop(); self.mutations.mark_dirty_scope(scopeid); } @@ -131,63 +135,26 @@ impl<'b> DiffState<'b> { pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { use VNode::{Component, Element, Fragment, Placeholder, Text}; match (old_node, new_node) { - // Check the most common cases first - // these are *actual* elements, not wrappers around lists (Text(old), Text(new)) => { - if std::ptr::eq(old, new) { - return; - } - - let root = old - .id - .get() - .expect("existing text nodes should have an ElementId"); - - if old.text != new.text { - self.mutations.set_text(new.text, root.as_u64()); - } - self.scopes.update_node(new_node, root); - - new.id.set(Some(root)); + self.diff_text_nodes(old, new, old_node, new_node); } (Placeholder(old), Placeholder(new)) => { - if std::ptr::eq(old, new) { - return; - } - - let root = old - .id - .get() - .expect("existing placeholder nodes should have an ElementId"); - - self.scopes.update_node(new_node, root); - new.id.set(Some(root)); + self.diff_placeholder_nodes(old, new, old_node, new_node); } (Element(old), Element(new)) => { - if std::ptr::eq(old, new) { - return; - } self.diff_element_nodes(old, new, old_node, new_node); } - // These two sets are pointers to nodes but are not actually nodes themselves (Component(old), Component(new)) => { - if std::ptr::eq(old, new) { - return; - } self.diff_component_nodes(old_node, new_node, *old, *new); } (Fragment(old), Fragment(new)) => { - if std::ptr::eq(old, new) { - return; - } self.diff_fragment_nodes(old, new); } - // Anything else is just a basic replace and create ( Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), @@ -322,6 +289,53 @@ impl<'b> DiffState<'b> { created } + pub(crate) fn diff_text_nodes( + &mut self, + old: &'b VText<'b>, + new: &'b VText<'b>, + _old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + ) { + if std::ptr::eq(old, new) { + return; + } + + // if the node is comming back not assigned, that means it was borrowed but removed + let root = match old.id.get() { + Some(id) => id, + None => self.scopes.reserve_node(new_node), + }; + + if old.text != new.text { + self.mutations.set_text(new.text, root.as_u64()); + } + + self.scopes.update_node(new_node, root); + + new.id.set(Some(root)); + } + + pub(crate) fn diff_placeholder_nodes( + &mut self, + old: &'b VPlaceholder, + new: &'b VPlaceholder, + _old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + ) { + if std::ptr::eq(old, new) { + return; + } + + // if the node is comming back not assigned, that means it was borrowed but removed + let root = match old.id.get() { + Some(id) => id, + None => self.scopes.reserve_node(new_node), + }; + + self.scopes.update_node(new_node, root); + new.id.set(Some(root)); + } + fn diff_element_nodes( &mut self, old: &'b VElement<'b>, @@ -329,6 +343,10 @@ impl<'b> DiffState<'b> { old_node: &'b VNode<'b>, new_node: &'b VNode<'b>, ) { + if std::ptr::eq(old, new) { + return; + } + // if the node is comming back not assigned, that means it was borrowed but removed let root = match old.id.get() { Some(id) => id, @@ -490,6 +508,10 @@ impl<'b> DiffState<'b> { } fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { + if std::ptr::eq(old, new) { + return; + } + // This is the case where options or direct vnodes might be used. // In this case, it's faster to just skip ahead to their diff if old.children.len() == 1 && new.children.len() == 1 { @@ -1106,9 +1128,7 @@ impl<'b> DiffState<'b> { for child in el.children.iter() { num_on_stack += self.push_all_nodes(child); } - self.mutations.push_root(el.id.get().unwrap()); - num_on_stack + 1 } } From 1913ccd61f24d3b23aecc0fe3151716fec2ea1ec Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 00:35:10 -0500 Subject: [PATCH 088/103] chore: current_scope always panics --- packages/core/src/diff.rs | 44 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 06d8346b4..d7189a710 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -208,9 +208,7 @@ impl<'b> DiffState<'b> { { self.mutations.create_element(tag_name, *namespace, real_id); - let cur_scope_id = self - .current_scope() - .expect("diffing should always have a scope"); + let cur_scope_id = self.current_scope(); for listener in listeners.iter() { listener.mounted_node.set(Some(real_id)); @@ -235,7 +233,7 @@ impl<'b> DiffState<'b> { } fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { - let parent_idx = self.current_scope().unwrap(); + let parent_idx = self.current_scope(); // the component might already exist - if it does, we need to reuse it // this makes figure out when to drop the component more complicated @@ -401,25 +399,25 @@ impl<'b> DiffState<'b> { // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) // // TODO: take a more efficient path than this - if let Some(cur_scope_id) = self.current_scope() { - if old.listeners.len() == new.listeners.len() { - for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { - if old_l.event != new_l.event { - self.mutations - .remove_event_listener(old_l.event, root.as_u64()); - self.mutations.new_event_listener(new_l, cur_scope_id); - } - new_l.mounted_node.set(old_l.mounted_node.get()); - } - } else { - for listener in old.listeners { + let cur_scope_id = self.current_scope(); + + if old.listeners.len() == new.listeners.len() { + for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { + if old_l.event != new_l.event { self.mutations - .remove_event_listener(listener.event, root.as_u64()); - } - for listener in new.listeners { - listener.mounted_node.set(Some(root)); - self.mutations.new_event_listener(listener, cur_scope_id); + .remove_event_listener(old_l.event, root.as_u64()); + self.mutations.new_event_listener(new_l, cur_scope_id); } + new_l.mounted_node.set(old_l.mounted_node.get()); + } + } else { + for listener in old.listeners { + self.mutations + .remove_event_listener(listener.event, root.as_u64()); + } + for listener in new.listeners { + listener.mounted_node.set(Some(root)); + self.mutations.new_event_listener(listener, cur_scope_id); } } @@ -1057,8 +1055,8 @@ impl<'b> DiffState<'b> { self.mutations.insert_before(first, created as u32); } - fn current_scope(&self) -> Option { - self.scope_stack.last().copied() + fn current_scope(&self) -> ScopeId { + self.scope_stack.last().copied().expect("no current scope") } fn enter_scope(&mut self, scope: ScopeId) { From 4135fabb8d496bd059b9733b910014976086672f Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 09:24:35 +0100 Subject: [PATCH 089/103] try fix docker folder --- docker | 1 - 1 file changed, 1 deletion(-) delete mode 160000 docker diff --git a/docker b/docker deleted file mode 160000 index 022d6d785..000000000 --- a/docker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 022d6d7850dba2e6f68edab3a89e383c8181321c From 74cc2eee813430ff0e521f70f31e710107e17ea1 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 09:26:01 +0100 Subject: [PATCH 090/103] adding docker folder --- docker/Dockerfile_pre_test | 9 +++++++++ docker/Dockerfile_test | 8 ++++++++ docker/README.md | 27 +++++++++++++++++++++++++++ docker/run_local_tests.sh | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 docker/Dockerfile_pre_test create mode 100644 docker/Dockerfile_test create mode 100644 docker/README.md create mode 100644 docker/run_local_tests.sh diff --git a/docker/Dockerfile_pre_test b/docker/Dockerfile_pre_test new file mode 100644 index 000000000..6e0af4987 --- /dev/null +++ b/docker/Dockerfile_pre_test @@ -0,0 +1,9 @@ +FROM rust:1.58-buster + +RUN apt update +RUN apt install -y libglib2.0-dev libgtk-3-dev libsoup2.4-dev libappindicator3-dev libwebkit2gtk-4.0-dev firefox-esr + +RUN cargo install cargo-make --debug +RUN cargo install cargo-cache && cargo cache -a + +CMD ["echo","\"base image built\""] diff --git a/docker/Dockerfile_test b/docker/Dockerfile_test new file mode 100644 index 000000000..f6265c2bb --- /dev/null +++ b/docker/Dockerfile_test @@ -0,0 +1,8 @@ +FROM dioxus-base-test-image + +RUN mkdir run_test +COPY tmp /run_test +WORKDIR /run_test +RUN cargo make tests + +CMD ["exit"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..57578f405 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,27 @@ +# Why this? + +This part is used to test whole package before pushing it + +# How to use it? + +Just run in the folder: +`bash run_local_tests.sh`. If nothing fails, then you can push your code to the repo. +or run: +`bash run_local_tests.sh --with-full-docker-cleanup` +for cleaning up images as well + +# How is it composed of? + + 1. `Dockerfile_pre_test` will build the base image for the tests to be run into + 2. `Dockerfile_test` will run the actual tests based on 1. + 3. `run_local_tests.sh` to wrap this up + +# Warning + +The task requires some amount of CPU work and disk space (5GB per tests). Some clean up is included in the script. + +# Requirements + + * [docker](https://docs.docker.com/engine/install/) + * bash + * rsync \ No newline at end of file diff --git a/docker/run_local_tests.sh b/docker/run_local_tests.sh new file mode 100644 index 000000000..0d6c1fb8b --- /dev/null +++ b/docker/run_local_tests.sh @@ -0,0 +1,36 @@ +set -eux + +echo "Test script started" + +function run_script { + if [[ -d tmp ]] + then + rm -rf tmp + mkdir tmp + else + mkdir tmp + fi + + # copy files first + rsync -a --progress ../ tmp --exclude target --exclude docker + + # build base image + docker build -f Dockerfile_pre_test -t dioxus-base-test-image . + # run test + docker build -f Dockerfile_test -t dioxus-test-image . + + # clean up + rm -rf tmp + if [ $1 = "--with-full-docker-cleanup" ] + then + docker image rm dioxus-base-test-image + docker image rm dioxus-test-image + docker system prune -a --force + fi +} + +run_script || echo "Error occured.. cleaning a bit." && + docker system prune -a --force && \ + rm -rf tmp; + +echo "Script finished to execute" From ee49fc27ffd6977e0ac62dc81d2e4604c975415d Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 09:28:06 +0100 Subject: [PATCH 091/103] fix some newlines --- packages/mobile/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/router/Makefile.toml | 2 +- packages/router/webdriver.json | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 50647a693..cdf0a5c05 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -17,4 +17,4 @@ dioxus-desktop = { path = "../desktop", version = "^0.1.6" } [lib] doctest = false # tests suspended until package ready -test = false \ No newline at end of file +test = false diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index b35760889..239a3c5d8 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -57,4 +57,4 @@ gloo-utils = "0.1.2" version = "0.3" features = [ "Document", -] \ No newline at end of file +] diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml index 989b1cc7b..063585c98 100644 --- a/packages/router/Makefile.toml +++ b/packages/router/Makefile.toml @@ -7,4 +7,4 @@ args = [ "--", "--features", "${DIOXUS_TEST_FEATURES}", -] \ No newline at end of file +] diff --git a/packages/router/webdriver.json b/packages/router/webdriver.json index 5f6013c9d..81413f24b 100644 --- a/packages/router/webdriver.json +++ b/packages/router/webdriver.json @@ -7,4 +7,5 @@ }, "args": [] } - } \ No newline at end of file + } + \ No newline at end of file From fed4a68650082d6dedefee0cf26efea3cffba276 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 10:01:45 +0100 Subject: [PATCH 092/103] improve script --- docker/run_local_tests.sh | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docker/run_local_tests.sh b/docker/run_local_tests.sh index 0d6c1fb8b..804d87afb 100644 --- a/docker/run_local_tests.sh +++ b/docker/run_local_tests.sh @@ -6,11 +6,8 @@ function run_script { if [[ -d tmp ]] then rm -rf tmp - mkdir tmp - else - mkdir tmp fi - + mkdir tmp # copy files first rsync -a --progress ../ tmp --exclude target --exclude docker @@ -21,16 +18,21 @@ function run_script { # clean up rm -rf tmp - if [ $1 = "--with-full-docker-cleanup" ] + if [ $# -ge 1 ] then - docker image rm dioxus-base-test-image - docker image rm dioxus-test-image - docker system prune -a --force + echo "Got some parameter" + if [ $1 = "--with-full-docker-cleanup" ] + then + docker image rm dioxus-base-test-image + docker image rm dioxus-test-image + docker system prune -af + fi fi } -run_script || echo "Error occured.. cleaning a bit." && - docker system prune -a --force && \ - rm -rf tmp; +run_script || echo "Error occured.. cleaning a bit." && \ + docker system prune -af; + +docker system prune -af echo "Script finished to execute" From 474fd8453855adba3657132948f6bf6ba47bd98d Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 18:14:50 +0100 Subject: [PATCH 093/103] modify folder structure --- {docker => .docker}/Dockerfile_pre_test | 0 {docker => .docker}/Dockerfile_test | 0 {docker => .docker}/README.md | 0 {docker => .docker}/run_local_tests.sh | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {docker => .docker}/Dockerfile_pre_test (100%) rename {docker => .docker}/Dockerfile_test (100%) rename {docker => .docker}/README.md (100%) rename {docker => .docker}/run_local_tests.sh (100%) diff --git a/docker/Dockerfile_pre_test b/.docker/Dockerfile_pre_test similarity index 100% rename from docker/Dockerfile_pre_test rename to .docker/Dockerfile_pre_test diff --git a/docker/Dockerfile_test b/.docker/Dockerfile_test similarity index 100% rename from docker/Dockerfile_test rename to .docker/Dockerfile_test diff --git a/docker/README.md b/.docker/README.md similarity index 100% rename from docker/README.md rename to .docker/README.md diff --git a/docker/run_local_tests.sh b/.docker/run_local_tests.sh similarity index 100% rename from docker/run_local_tests.sh rename to .docker/run_local_tests.sh From a9ac0568e24d1ddaa4a896bedec1cc2b32bb25c6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 13:19:41 -0500 Subject: [PATCH 094/103] fix: allow prevent_default on svg --- examples/svg.rs | 99 ++++++++++++++++++++++++++ packages/html/src/elements.rs | 7 +- packages/html/src/global_attributes.rs | 16 +++++ 3 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 examples/svg.rs diff --git a/examples/svg.rs b/examples/svg.rs new file mode 100644 index 000000000..781b35f2c --- /dev/null +++ b/examples/svg.rs @@ -0,0 +1,99 @@ +use dioxus::{events::MouseEvent, prelude::*}; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let (val, set_val) = use_state(&cx, || 5); + + cx.render(rsx! { + div { + user_select: "none", + webkit_user_select: "none", + margin_left: "10%", + margin_right: "10%", + h1 { "Click die to generate a new value" } + div { + cursor: "pointer", + height: "80%", + width: "80%", + Die { + value: *val, + keep: true, + onclick: move |_| { + use rand::Rng; + let mut rng = rand::thread_rng(); + set_val(rng.gen_range(1..6)); + } + } + } + } + }) +} + +#[derive(Props)] +pub struct DieProps<'a> { + pub value: u64, + pub keep: bool, + pub onclick: EventHandler<'a, MouseEvent>, +} + +const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)]; +const DOTS_FOR_VALUE: [[bool; 7]; 6] = [ + [false, false, false, false, false, false, true], + [false, false, true, true, false, false, false], + [false, false, true, true, false, false, true], + [true, false, true, true, false, true, false], + [true, false, true, true, false, true, true], + [true, true, true, true, true, true, false], +]; + +const OFFSET: i64 = 600; +const DOT_RADIUS: &str = "200"; +const HELD_COLOR: &str = "#aaa"; +const UNHELD_COLOR: &str = "#ddd"; + +// A six-sided die (D6) with dots. +#[allow(non_snake_case)] +pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element { + let &DieProps { value, keep, .. } = cx.props; + + let active_dots = &DOTS_FOR_VALUE[(value - 1) as usize]; + let fill = if keep { HELD_COLOR } else { UNHELD_COLOR }; + let dots = DOTS + .iter() + .zip(active_dots.iter()) + .filter(|(_, &active)| active) + .map(|((x, y), _)| { + let dcx = x * OFFSET; + let dcy = y * OFFSET; + rsx!(circle { + cx: "{dcx}", + cy: "{dcy}", + r: "{DOT_RADIUS}", + fill: "#333" + }) + }); + + rsx!(cx, + svg { + onclick: move |e| cx.props.onclick.call(e), + prevent_default: "onclick", + "dioxus-prevent-default": "onclick", + class: "die", + view_box: "-1000 -1000 2000 2000", + + rect { + x: "-1000", + y: "-1000", + width: "2000", + height: "2000", + rx: "{DOT_RADIUS}", + fill: "{fill}", + } + + dots + } + ) +} diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 1a2f36e57..c6b1cb7e0 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1047,7 +1047,7 @@ impl input { /// - `text` /// - `time` /// - `url` - /// - `week` + /// - `week` pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("type", val, None, false) } @@ -1100,11 +1100,6 @@ impl label { cx.attr("for", val, None, false) } } -impl a { - pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { - cx.attr("dioxus-prevent-default", val, None, false) - } -} builder_constructors! { // SVG components diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 1368f87e3..151a1539b 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -49,6 +49,10 @@ macro_rules! aria_trait_methods { } pub trait GlobalAttributes { + /// Prevent the default action for this element. + /// + /// For more information, see the MDN docs: + /// fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("dioxus-prevent-default", val, None, false) } @@ -603,6 +607,11 @@ pub trait GlobalAttributes { /// Specifies the speed curve of the transition effect. transition_timing_function: "transition-timing-function", + /// The user-select CSS property controls whether the user can select text. + /// This doesn't have any effect on content loaded as part of a browser's user interface (its chrome), except in textboxes. + user_select: "user-select", + webkit_user_select: "-webkit-user-select", + /// Sets the vertical positioning of an element relative to the current text baseline. vertical_align: "vertical-align", @@ -685,6 +694,13 @@ pub trait GlobalAttributes { } pub trait SvgAttributes { + /// Prevent the default action for this element. + /// + /// For more information, see the MDN docs: + /// + fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("dioxus-prevent-default", val, None, false) + } aria_trait_methods! { accent_height: "accent-height", accumulate: "accumulate", From 89cfda4bf941269879eab4ea630b063e5fda2152 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 13:21:20 -0500 Subject: [PATCH 095/103] fix: add attributions --- examples/svg.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/svg.rs b/examples/svg.rs index 781b35f2c..6e5676fe3 100644 --- a/examples/svg.rs +++ b/examples/svg.rs @@ -1,3 +1,5 @@ +// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example! + use dioxus::{events::MouseEvent, prelude::*}; fn main() { From ac4faec570999cb135c75b519da7bf4f72d2ec16 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 13:30:24 -0500 Subject: [PATCH 096/103] fix: remove broken example --- packages/desktop/examples/async.rs | 35 ------------------------------ 1 file changed, 35 deletions(-) delete mode 100644 packages/desktop/examples/async.rs diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs deleted file mode 100644 index 0668834f9..000000000 --- a/packages/desktop/examples/async.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* use dioxus::prelude::*; -use dioxus_core as dioxus; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; -use std::time::Duration; - -fn main() { - dioxus_desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let (count, set_count) = use_state(&cx, || 0); - - use_future(&cx, || { - let set_count = set_count.clone(); - async move { - loop { - tokio::time::sleep(Duration::from_millis(1000)).await; - set_count.modify(|f| f + 1) - } - } - }); - - cx.render(rsx! { - div { - h1 { "High-Five counter: {count}" } - button { - onclick: move |_| set_count(0), - "Click me!" - } - } - }) -} - */ From ca0d7dc268a6ebb980f790206ebd79ce16e7bad2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 4 Feb 2022 01:56:54 -0500 Subject: [PATCH 097/103] fix: remove code generation --- packages/interpreter/Cargo.toml | 8 + packages/interpreter/build.rs | 16 - .../build.rs => interpreter/src/bindings.rs} | 25 +- .../interpreter/{gen => src}/interpreter.js | 0 packages/interpreter/src/interpreter.ts | 675 ------------------ packages/interpreter/src/lib.rs | 8 +- packages/interpreter/tsconfig.json | 15 - packages/web/Cargo.toml | 24 +- packages/web/src/bindings.rs | 561 --------------- packages/web/src/dom.rs | 2 +- packages/web/src/lib.rs | 1 - 11 files changed, 31 insertions(+), 1304 deletions(-) delete mode 100644 packages/interpreter/build.rs rename packages/{web/build.rs => interpreter/src/bindings.rs} (77%) rename packages/interpreter/{gen => src}/interpreter.js (100%) delete mode 100644 packages/interpreter/src/interpreter.ts delete mode 100644 packages/interpreter/tsconfig.json delete mode 100644 packages/web/src/bindings.rs diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index fe150e1f9..1e0f4bb51 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -14,3 +14,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +wasm-bindgen = { version = "0.2.79", optional = true } +js-sys = { version = "0.3.56", optional = true } +web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } + + +[features] +default = [] +web = ["wasm-bindgen", "js-sys", "web-sys"] diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs deleted file mode 100644 index 610e3c3c3..000000000 --- a/packages/interpreter/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::process::Command; - -fn main() { - println!("cargo:rerun-if-changed=interpreter.ts"); - - match Command::new("tsc").spawn() { - Ok(_) => println!("Was spawned :)"), - Err(e) => { - if let std::io::ErrorKind::NotFound = e.kind() { - println!("`tsc` was not found! Not going to generate new interpreter") - } else { - println!("Some strange error occurred :("); - } - } - } -} diff --git a/packages/web/build.rs b/packages/interpreter/src/bindings.rs similarity index 77% rename from packages/web/build.rs rename to packages/interpreter/src/bindings.rs index 3c9baa7c8..fa96e3230 100644 --- a/packages/web/build.rs +++ b/packages/interpreter/src/bindings.rs @@ -1,18 +1,9 @@ -use std::{fs::File, io::Write}; - -fn main() { - let src = format!( - r###"use js_sys::Function; +use js_sys::Function; use wasm_bindgen::prelude::*; -use web_sys::{{Element, Node}}; +use web_sys::{Element, Node}; -/* -This is an autogenerated file from build.rs. -Do not edit this file directly. -*/ - -#[wasm_bindgen(inline_js = r##"{}"##)] -extern "C" {{ +#[wasm_bindgen(module = "/src/interpreter.js")] +extern "C" { pub type Interpreter; #[wasm_bindgen(constructor)] @@ -65,12 +56,4 @@ extern "C" {{ #[wasm_bindgen(method)] pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); -}} -"###, - dioxus_interpreter_js::INTERPRTER_JS - ); - - // write the bindings to a local file - let mut file = File::create("src/bindings.rs").unwrap(); - file.write_all(src.as_bytes()).unwrap(); } diff --git a/packages/interpreter/gen/interpreter.js b/packages/interpreter/src/interpreter.js similarity index 100% rename from packages/interpreter/gen/interpreter.js rename to packages/interpreter/src/interpreter.js diff --git a/packages/interpreter/src/interpreter.ts b/packages/interpreter/src/interpreter.ts deleted file mode 100644 index f150410f4..000000000 --- a/packages/interpreter/src/interpreter.ts +++ /dev/null @@ -1,675 +0,0 @@ -export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} - -declare global { - interface Window { - interpreter: Interpreter; - rpc: { call: (method: string, args?: any) => void }; - } -} - - -export class Interpreter { - root: Element; - stack: Element[]; - listeners: { [key: string]: number }; - handlers: { [key: string]: (evt: Event) => void }; - lastNodeWasText: boolean; - nodes: Element[]; - - - constructor(root: Element) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - - top() { - return this.stack[this.stack.length - 1]; - } - - pop() { - return this.stack.pop(); - } - - PushRoot(root: number) { - const node = this.nodes[root]; - this.stack.push(node); - } - - AppendChildren(many: number) { - let root = this.stack[this.stack.length - (1 + many)]; - - let to_add = this.stack.splice(this.stack.length - many); - - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - - ReplaceWith(root_id: number, m: number) { - let root = this.nodes[root_id] as Element; - let els = this.stack.splice(this.stack.length - m); - - root.replaceWith(...els); - } - - InsertAfter(root: number, n: number) { - let old = this.nodes[root] as Element; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - - InsertBefore(root: number, n: number) { - let old = this.nodes[root] as Element; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - - Remove(root: number) { - let node = this.nodes[root] as Element; - if (node !== undefined) { - node.remove(); - } - } - - CreateTextNode(text: string, root: number) { - // todo: make it so the types are okay - const node = document.createTextNode(text) as any as Element; - this.nodes[root] = node; - this.stack.push(node); - } - - CreateElement(tag: string, root: number) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - - this.nodes[root] = el; - this.stack.push(el); - } - - CreateElementNs(tag: string, root: number, ns: string) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - - CreatePlaceholder(root: number) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - - NewEventListener(event_name: string, root: number, handler: (evt: Event) => void) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } else { - this.listeners[event_name]++; - } - } - - RemoveEventListener(root: number, event_name: string) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - - this.listeners[event_name]--; - - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - - - SetText(root: number, text: string) { - this.nodes[root].textContent = text; - } - - SetAttribute(root: number, field: string, value: string, ns: string | undefined) { - const name = field; - const node = this.nodes[root]; - - if (ns == "style") { - - // @ts-ignore - (node as HTMLElement).style[name] = value; - - } else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } else { - switch (name) { - case "value": - if (value != (node as HTMLInputElement).value) { - (node as HTMLInputElement).value = value; - } - break; - case "checked": - (node as HTMLInputElement).checked = value === "true"; - break; - case "selected": - (node as HTMLOptionElement).selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root: number, name: string) { - - const node = this.nodes[root]; - node.removeAttribute(name); - - if (name === "value") { - (node as HTMLInputElement).value = ""; - } - - if (name === "checked") { - (node as HTMLInputElement).checked = false; - } - - if (name === "selected") { - (node as HTMLOptionElement).selected = false; - } - } - - handleEdits(edits: DomEdit[]) { - this.stack.push(this.root); - - for (let edit of edits) { - this.handleEdit(edit); - } - } - - handleEdit(edit: DomEdit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - - - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event: Event) => { - let target = event.target as Element | null; - - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href") - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - - let contents = serialize_event(event); - - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - - if (event.type == "submit") { - event.preventDefault(); - } - - - if (realId == null) { - return; - } - - window.rpc.call("user_event", { - event: (edit as NewEventListener).event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} - - - -function serialize_event(event: Event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = (event as CompositionEvent); - return { - data, - }; - } - - case "keydown": - case "keypress": - case "keyup": { - let { - charCode, - key, - altKey, - ctrlKey, - metaKey, - keyCode, - shiftKey, - location, - repeat, - which, - } = (event as KeyboardEvent); - - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - - case "focus": - case "blur": { - return {}; - } - - case "change": { - let target = event.target as HTMLInputElement; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } else { - value = target.value ?? target.textContent; - } - - return { - value: value, - }; - } - - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target as HTMLFormElement; - let value = target.value ?? target.textContent; - - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - - return { - value: value, - }; - } - - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - } = event as MouseEvent; - - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - pointerId, - width, - height, - pressure, - tangentialPressure, - tiltX, - tiltY, - twist, - pointerType, - isPrimary, - } = event as PointerEvent; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - - case "select": { - return {}; - } - - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { - altKey, - ctrlKey, - metaKey, - shiftKey, - } = event as TouchEvent; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - - case "scroll": { - return {}; - } - - case "wheel": { - const { - deltaX, - deltaY, - deltaZ, - deltaMode, - } = event as WheelEvent; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - - case "animationstart": - case "animationend": - case "animationiteration": { - const { - animationName, - elapsedTime, - pseudoElement, - } = event as AnimationEvent; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - - case "transitionend": { - const { - propertyName, - elapsedTime, - pseudoElement, - } = event as TransitionEvent; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - - case "toggle": { - return {}; - } - - default: { - return {}; - } - } -} - -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; - - - -type PushRoot = { type: "PushRoot", root: number }; -type AppendChildren = { type: "AppendChildren", many: number }; -type ReplaceWith = { type: "ReplaceWith", root: number, m: number }; -type InsertAfter = { type: "InsertAfter", root: number, n: number }; -type InsertBefore = { type: "InsertBefore", root: number, n: number }; -type Remove = { type: "Remove", root: number }; -type CreateTextNode = { type: "CreateTextNode", text: string, root: number }; -type CreateElement = { type: "CreateElement", tag: string, root: number }; -type CreateElementNs = { type: "CreateElementNs", tag: string, root: number, ns: string }; -type CreatePlaceholder = { type: "CreatePlaceholder", root: number }; -type NewEventListener = { type: "NewEventListener", root: number, event_name: string, scope: number }; -type RemoveEventListener = { type: "RemoveEventListener", event_name: string, scope: number, root: number }; -type SetText = { type: "SetText", root: number, text: string }; -type SetAttribute = { type: "SetAttribute", root: number, field: string, value: string, ns: string | undefined }; -type RemoveAttribute = { type: "RemoveAttribute", root: number, name: string }; - - -type DomEdit = - PushRoot | - AppendChildren | - ReplaceWith | - InsertAfter | - InsertBefore | - Remove | - CreateTextNode | - CreateElement | - CreateElementNs | - CreatePlaceholder | - NewEventListener | - RemoveEventListener | - SetText | - SetAttribute | - RemoveAttribute; diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 7d680ae25..ebca3cbf4 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1 +1,7 @@ -pub static INTERPRTER_JS: &str = include_str!("../gen/interpreter.js"); +pub static INTERPRTER_JS: &str = include_str!("./interpreter.js"); + +#[cfg(feature = "web")] +mod bindings; + +#[cfg(feature = "web")] +pub use bindings::Interpreter; diff --git a/packages/interpreter/tsconfig.json b/packages/interpreter/tsconfig.json deleted file mode 100644 index 1c1ed1ef9..000000000 --- a/packages/interpreter/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ES2015", - "lib": [ - "es2015", - "es5", - "es6", - "dom" - ], - "rootDir": "src", - "strict": true, - "outDir": "gen", - } -} diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 9ac508a8c..9145fed8b 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -13,27 +13,25 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9" } dioxus-html = { path = "../html", version = "^0.1.6" } -js-sys = "0.3" -wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +js-sys = "0.3.56" +wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] } lazy_static = "1.4.0" -wasm-bindgen-futures = "0.4.20" +wasm-bindgen-futures = "0.4.29" log = { version = "0.4.14", features = ["release_max_level_off"] } fxhash = "0.2.1" wasm-logger = "0.2.0" console_error_panic_hook = { version = "0.1.7", optional = true } -wasm-bindgen-test = "0.3.21" -once_cell = "1.8" +wasm-bindgen-test = "0.3.29" +once_cell = "1.9.0" async-channel = "1.6.1" -anyhow = "1.0" -gloo-timers = { version = "0.2.1", features = ["futures"] } -futures-util = "0.3.15" +anyhow = "1.0.53" +gloo-timers = { version = "0.2.3", features = ["futures"] } +futures-util = "0.3.19" smallstr = "0.2.0" - -[build-dependencies] -dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } +dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] } [dependencies.web-sys] -version = "0.3.51" +version = "0.3.56" features = [ "Comment", "Attr", @@ -78,5 +76,5 @@ panic_hook = ["console_error_panic_hook"] [dev-dependencies] dioxus-core-macro = { path = "../core-macro" } -wasm-bindgen-test = "0.3.28" +wasm-bindgen-test = "0.3.29" dioxus-ssr = { path = "../ssr" } diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs deleted file mode 100644 index 4834146af..000000000 --- a/packages/web/src/bindings.rs +++ /dev/null @@ -1,561 +0,0 @@ -use js_sys::Function; -use wasm_bindgen::prelude::*; -use web_sys::{Element, Node}; - -/* -This is an autogenerated file from build.rs. -Do not edit this file directly. -*/ - -#[wasm_bindgen(inline_js = r##"export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} -export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); - } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } - else { - value = target.value ?? target.textContent; - } - return { - value: value, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; -"##)] -extern "C" { - pub type Interpreter; - - #[wasm_bindgen(constructor)] - pub fn new(arg: Element) -> Interpreter; - - #[wasm_bindgen(method)] - pub fn set_node(this: &Interpreter, id: usize, node: Node); - - #[wasm_bindgen(method)] - pub fn PushRoot(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn AppendChildren(this: &Interpreter, many: u32); - - #[wasm_bindgen(method)] - pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32); - - #[wasm_bindgen(method)] - pub fn InsertAfter(this: &Interpreter, root: u64, n: u32); - - #[wasm_bindgen(method)] - pub fn InsertBefore(this: &Interpreter, root: u64, n: u32); - - #[wasm_bindgen(method)] - pub fn Remove(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateTextNode(this: &Interpreter, text: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElement(this: &Interpreter, tag: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str); - - #[wasm_bindgen(method)] - pub fn CreatePlaceholder(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function); - - #[wasm_bindgen(method)] - pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str); - - #[wasm_bindgen(method)] - pub fn SetText(this: &Interpreter, root: u64, text: &str); - - #[wasm_bindgen(method)] - pub fn SetAttribute(this: &Interpreter, root: u64, field: &str, value: &str, ns: Option<&str>); - - #[wasm_bindgen(method)] - pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); -} diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 733e50e4b..6ca689d28 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,8 +7,8 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use crate::bindings::Interpreter; use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent}; +use dioxus_interpreter_js::Interpreter; use js_sys::Function; use std::{any::Any, rc::Rc, sync::Arc}; use wasm_bindgen::{closure::Closure, JsCast}; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index f4266a7c8..cef1bc14c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -61,7 +61,6 @@ pub use dioxus_core as dioxus; use dioxus_core::prelude::Component; use futures_util::FutureExt; -pub(crate) mod bindings; mod cache; mod cfg; mod dom; From b4391a3eaa4292d96072661107fd7328c23293b2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 4 Feb 2022 02:44:27 -0500 Subject: [PATCH 098/103] fix: tweak the js bindings while we're at it --- examples/todomvc.rs | 9 +- packages/interpreter/src/interpreter.js | 1002 ++++++++++++----------- 2 files changed, 531 insertions(+), 480 deletions(-) diff --git a/examples/todomvc.rs b/examples/todomvc.rs index e5f7d1dfa..937d6992c 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -120,15 +120,18 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { rsx!(cx, li { class: "{completed} {editing}", - onclick: move |_| set_is_editing(true), - onfocusout: move |_| set_is_editing(false), div { class: "view", input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}", onchange: move |evt| { cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap(); } } - label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" } + label { + r#for: "cbg-{todo.id}", + onclick: move |_| set_is_editing(true), + onfocusout: move |_| set_is_editing(false), + "{todo.contents}" + } } is_editing.then(|| rsx!{ input { diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 0d89efa85..dbcb73a96 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -1,497 +1,545 @@ export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.rpc.call("initialize"); + } } export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; + root; + stack; + listeners; + handlers; + lastNodeWasText; + nodes; + constructor(root) { + this.root = root; + this.stack = [root]; + this.listeners = {}; + this.handlers = {}; + this.lastNodeWasText = false; + this.nodes = [root]; + } + top() { + return this.stack[this.stack.length - 1]; + } + pop() { + return this.stack.pop(); + } + PushRoot(root) { + const node = this.nodes[root]; + this.stack.push(node); + } + AppendChildren(many) { + let root = this.stack[this.stack.length - (1 + many)]; + let to_add = this.stack.splice(this.stack.length - many); + for (let i = 0; i < many; i++) { + root.appendChild(to_add[i]); } - top() { - return this.stack[this.stack.length - 1]; + } + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; + let els = this.stack.splice(this.stack.length - m); + root.replaceWith(...els); + } + InsertAfter(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.after(...new_nodes); + } + InsertBefore(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.before(...new_nodes); + } + Remove(root) { + let node = this.nodes[root]; + if (node !== undefined) { + node.remove(); } - pop() { - return this.stack.pop(); + } + CreateTextNode(text, root) { + // todo: make it so the types are okay + const node = document.createTextNode(text); + this.nodes[root] = node; + this.stack.push(node); + } + CreateElement(tag, root) { + const el = document.createElement(tag); + // el.setAttribute("data-dioxus-id", `${root}`); + this.nodes[root] = el; + this.stack.push(el); + } + CreateElementNs(tag, root, ns) { + let el = document.createElementNS(ns, tag); + this.stack.push(el); + this.nodes[root] = el; + } + CreatePlaceholder(root) { + let el = document.createElement("pre"); + el.hidden = true; + this.stack.push(el); + this.nodes[root] = el; + } + NewEventListener(event_name, root, handler) { + const element = this.nodes[root]; + element.setAttribute("data-dioxus-id", `${root}`); + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } else { + this.listeners[event_name]++; } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); + } + RemoveEventListener(root, event_name) { + const element = this.nodes[root]; + element.removeAttribute(`data-dioxus-id`); + this.listeners[event_name]--; + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } + } + SetText(root, text) { + this.nodes[root].textContent = text; + } + SetAttribute(root, field, value, ns) { + const name = field; + const node = this.nodes[root]; + if (ns == "style") { + // @ts-ignore + node.style[name] = value; + } else if (ns != null || ns != undefined) { + node.setAttributeNS(ns, name, value); + } else { + switch (name) { + case "value": + if (value != node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value == "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } else { + node.setAttribute(name, value); + } + } } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); + } + RemoveAttribute(root, name) { + const node = this.nodes[root]; + node.removeAttribute(name); + if (name === "value") { + node.value = ""; } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); + if (name === "checked") { + node.checked = false; } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); + if (name === "selected") { + node.selected = false; } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } + } + handleEdits(edits) { + this.stack.push(this.root); + for (let edit of edits) { + this.handleEdit(edit); } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } + } + handleEdit(edit) { + switch (edit.type) { + case "PushRoot": + this.PushRoot(edit.root); + break; + case "AppendChildren": + this.AppendChildren(edit.many); + break; + case "ReplaceWith": + this.ReplaceWith(edit.root, edit.m); + break; + case "InsertAfter": + this.InsertAfter(edit.root, edit.n); + break; + case "InsertBefore": + this.InsertBefore(edit.root, edit.n); + break; + case "Remove": + this.Remove(edit.root); + break; + case "CreateTextNode": + this.CreateTextNode(edit.text, edit.root); + break; + case "CreateElement": + this.CreateElement(edit.tag, edit.root); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.root, edit.ns); + break; + case "CreatePlaceholder": + this.CreatePlaceholder(edit.root); + break; + case "RemoveEventListener": + this.RemoveEventListener(edit.root, edit.event_name); + break; + case "NewEventListener": + // this handler is only provided on desktop implementations since this + // method is not used by the web implementation + let handler = (event) => { + let target = event.target; + if (target != null) { + let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + + if (event.type == "click") { + // todo call prevent default if it's the right type of event + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined) { + window.rpc.call("browser_open", { href }); + } + } + } } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } + // walk the tree to find the real element + while (realId == null && target.parentElement != null) { + target = target.parentElement; + realId = target.getAttribute(`data-dioxus-id`); + } + + shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + let contents = serialize_event(event); + if (shouldPreventDefault === `on${event.type}`) { + // event.preventDefault(); + } + if (event.type == "submit") { + // event.preventDefault(); + } + if (realId == null) { + return; + } + window.rpc.call("user_event", { + event: edit.event_name, + mounted_dom_id: parseInt(realId), + contents: contents, + }); + } + }; + this.NewEventListener(edit.event_name, edit.root, handler); + break; + case "SetText": + this.SetText(edit.root, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.root, edit.name); + break; } + } } function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - let target = event.target; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } - else { - value = target.value ?? target.textContent; - } - return { - value: value, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } + switch (event.type) { + case "copy": + case "cut": + case "past": { + return {}; } + case "compositionend": + case "compositionstart": + case "compositionupdate": { + let { data } = event; + return { + data, + }; + } + case "keydown": + case "keypress": + case "keyup": { + let { + charCode, + key, + altKey, + ctrlKey, + metaKey, + keyCode, + shiftKey, + location, + repeat, + which, + } = event; + return { + char_code: charCode, + key: key, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + key_code: keyCode, + shift_key: shiftKey, + location: location, + repeat: repeat, + which: which, + locale: "locale", + }; + } + case "focus": + case "blur": { + return {}; + } + case "change": { + let target = event.target; + let value; + if (target.type === "checkbox" || target.type === "radio") { + value = target.checked ? "true" : "false"; + } else { + value = target.value ?? target.textContent; + } + return { + value: value, + }; + } + case "input": + case "invalid": + case "reset": + case "submit": { + let target = event.target; + let value = target.value ?? target.textContent; + if (target.type == "checkbox") { + value = target.checked ? "true" : "false"; + } + return { + value: value, + }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "drag": + case "dragend": + case "dragenter": + case "dragexit": + case "dragleave": + case "dragover": + case "dragstart": + case "drop": + case "mousedown": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "mouseup": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; + } + case "pointerdown": + case "pointermove": + case "pointerup": + case "pointercancel": + case "gotpointercapture": + case "lostpointercapture": + case "pointerenter": + case "pointerleave": + case "pointerover": + case "pointerout": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + pointerId, + width, + height, + pressure, + tangentialPressure, + tiltX, + tiltY, + twist, + pointerType, + isPrimary, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + pointer_id: pointerId, + width: width, + height: height, + pressure: pressure, + tangential_pressure: tangentialPressure, + tilt_x: tiltX, + tilt_y: tiltY, + twist: twist, + pointer_type: pointerType, + is_primary: isPrimary, + }; + } + case "select": { + return {}; + } + case "touchcancel": + case "touchend": + case "touchmove": + case "touchstart": { + const { altKey, ctrlKey, metaKey, shiftKey } = event; + return { + // changed_touches: event.changedTouches, + // target_touches: event.targetTouches, + // touches: event.touches, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, + }; + } + case "scroll": { + return {}; + } + case "wheel": { + const { deltaX, deltaY, deltaZ, deltaMode } = event; + return { + delta_x: deltaX, + delta_y: deltaY, + delta_z: deltaZ, + delta_mode: deltaMode, + }; + } + case "animationstart": + case "animationend": + case "animationiteration": { + const { animationName, elapsedTime, pseudoElement } = event; + return { + animation_name: animationName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "transitionend": { + const { propertyName, elapsedTime, pseudoElement } = event; + return { + property_name: propertyName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "abort": + case "canplay": + case "canplaythrough": + case "durationchange": + case "emptied": + case "encrypted": + case "ended": + case "error": + case "loadeddata": + case "loadedmetadata": + case "loadstart": + case "pause": + case "play": + case "playing": + case "progress": + case "ratechange": + case "seeked": + case "seeking": + case "stalled": + case "suspend": + case "timeupdate": + case "volumechange": + case "waiting": { + return {}; + } + case "toggle": { + return {}; + } + default: { + return {}; + } + } } const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, + allowfullscreen: true, + allowpaymentrequest: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, + truespeed: true, }; From ca9700a84dc01afcfa9e3e17e94143d1f33a335c Mon Sep 17 00:00:00 2001 From: t1m0t Date: Fri, 4 Feb 2022 08:47:09 +0100 Subject: [PATCH 099/103] fix ci test --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ab05df1c..29319ef30 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,8 +38,8 @@ jobs: with: command: install args: --debug cargo-make - - uses: actions-rs/cargo@v1 - uses: browser-actions/setup-firefox@latest + - uses: actions-rs/cargo@v1 with: command: make args: tests From bd565bb65f7cef7aab263cb28994181a7f069730 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Fri, 4 Feb 2022 17:18:31 +0100 Subject: [PATCH 100/103] improve Makefile tests --- .docker/Dockerfile_pre_test | 15 +++++++++++++-- .docker/run_local_tests.sh | 5 ++--- Makefile.toml | 27 ++++++++++++++++++--------- packages/router/Makefile.toml | 4 ++-- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.docker/Dockerfile_pre_test b/.docker/Dockerfile_pre_test index 6e0af4987..380bb5773 100644 --- a/.docker/Dockerfile_pre_test +++ b/.docker/Dockerfile_pre_test @@ -1,9 +1,20 @@ FROM rust:1.58-buster RUN apt update -RUN apt install -y libglib2.0-dev libgtk-3-dev libsoup2.4-dev libappindicator3-dev libwebkit2gtk-4.0-dev firefox-esr +RUN apt install -y \ + libglib2.0-dev \ + libgtk-3-dev \ + libsoup2.4-dev \ + libappindicator3-dev \ + libwebkit2gtk-4.0-dev \ + firefox-esr +# for kcov and Tarpaulin +#liblzma-dev binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev RUN cargo install cargo-make --debug +# for test coverage +#RUN cargo install cargo-tarpaulin +# clean up a bit RUN cargo install cargo-cache && cargo cache -a -CMD ["echo","\"base image built\""] +CMD ["exit"] diff --git a/.docker/run_local_tests.sh b/.docker/run_local_tests.sh index 804d87afb..4684625d4 100644 --- a/.docker/run_local_tests.sh +++ b/.docker/run_local_tests.sh @@ -25,14 +25,13 @@ function run_script { then docker image rm dioxus-base-test-image docker image rm dioxus-test-image - docker system prune -af fi fi } run_script || echo "Error occured.. cleaning a bit." && \ - docker system prune -af; + docker system prune -f; -docker system prune -af +docker system prune -f echo "Script finished to execute" diff --git a/Makefile.toml b/Makefile.toml index 95abec9de..17c32fd15 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -10,13 +10,6 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true namespace = "core" private = true -[tasks.tests] -category = "Testing" -dependencies = ["tests-setup"] -description = "Run all tests" -env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"]} -run_task = {name = ["test-flow"], fork = true} - [tasks.tests-setup] private = true script = [ @@ -31,12 +24,28 @@ script = [ ] script_runner = "@duckscript" +[tasks.tests] +category = "Testing" +dependencies = ["tests-setup"] +description = "Run all tests" +env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]} +run_task = {name = ["test-flow", "test-with-browser"], fork = true} + +[tasks.build] +command = "cargo" +args = ["build"] + [tasks.test-flow] dependencies = ["test"] private = true -workspace = true [tasks.test] -args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-mobile"] +dependencies = ["build"] command = "cargo" +args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-router"] private = true + +[tasks.test-with-browser] +env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router"] } +private = true +workspace = true diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml index 063585c98..42b7f5c9a 100644 --- a/packages/router/Makefile.toml +++ b/packages/router/Makefile.toml @@ -1,4 +1,4 @@ -[tasks.test] +[tasks.test-with-browser] extend = "core::wasm-pack-base" command = "wasm-pack" args = [ @@ -7,4 +7,4 @@ args = [ "--", "--features", "${DIOXUS_TEST_FEATURES}", -] +] \ No newline at end of file From c18670c068d9029e123f0daa450f579f0b7e9632 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Fri, 4 Feb 2022 17:52:47 +0100 Subject: [PATCH 101/103] adding cargo-make and wasm-pack to CI worklow --- .github/workflows/main.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29319ef30..244313ef9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,12 +33,9 @@ jobs: - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - - name: Install cargo-make - uses: actions-rs/cargo@v1 - with: - command: install - args: --debug cargo-make + - uses: davidB/rust-cargo-make@v1 - uses: browser-actions/setup-firefox@latest + - uses: jetli/wasm-pack-action@v0.3.0 - uses: actions-rs/cargo@v1 with: command: make From 841c0c40159972614d37a7bce6efba1e3c22928d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 4 Feb 2022 18:06:36 -0500 Subject: [PATCH 102/103] docs: combine icons --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 52493b119..cc39ddf82 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,7 @@ CI status - -