on develop help sign in
Merge pull request #275 from reflex-frp/ts-hydration

Hydration

commitac4dbc6d08056a860f8f59a1c305aa363e82e3d7 authorAli Abrar <aliabrar@gmail.com> dateFri 8 Mar 2019, 22:08 (8 years ago) parentaa521021069e54
20 files changed+3732−731
chrome-test-utils/LICENSE+12 chrome-test-utils/chrome-test-utils.cabal+30 chrome-test-utils/src/Test/Util/ChromeFlags.hs+16 chrome-test-utils/src/Test/Util/UnshareNetwork.hs+12 default.nix+1 reflex-dom-core/default.nix+24−13 reflex-dom-core/reflex-dom-core.cabal+51−5 reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+52−2 reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+1638−555 reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+71−4 reflex-dom-core/src/Reflex/Dom/Location.hs+39 reflex-dom-core/src/Reflex/Dom/Main.hs+126−17 reflex-dom-core/src/Reflex/Dom/Prerender.hs+183−50 reflex-dom-core/src/Reflex/Dom/Specializations.hs−62 reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+15 reflex-dom-core/test/gc.hs+12−11 reflex-dom-core/test/hydration.hs+1423 reflex-dom/benchmarks/krausest.hs+7−7 reflex-dom/src/Reflex/Dom.hs+2−1 reflex-dom/src/Reflex/Dom/Internal.hs+18−4
chrome-test-utils/LICENSEnew file+12
@@ -0,0 +1,12 @@
1+Copyright (c) 2019, Obsidian Systems LLC
2+All rights reserved.
3+
4+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
6+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7+
8+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9+
10+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11+
12+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
chrome-test-utils/chrome-test-utils.cabalnew file+30
@@ -0,0 +1,30 @@
1+Name: chrome-test-utils
2+Version: 0.4
3+Synopsis: Utilities to write headless Chromium tests
4+License: BSD3
5+License-file: LICENSE
6+Author: Obsidian Systems LLC
7+Maintainer: maintainer@obsidian.systems
8+Stability: Experimental
9+Build-type: Simple
10+Cabal-version: >=1.9.2
11+
12+library
13+ hs-source-dirs: src
14+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
15+ build-depends: base
16+ , linux-namespaces
17+ , process
18+ , temporary
19+ , text
20+ , unix
21+ exposed-modules:
22+ Test.Util.UnshareNetwork
23+ Test.Util.ChromeFlags
24+ if !os(linux) || !arch(x86_64)
25+ buildable: False
26+
27+source-repository head
28+ type: git
29+ location: https://github.com/reflex-frp/reflex-dom
30+ subdir: chrome-test-utils
chrome-test-utils/src/Test/Util/ChromeFlags.hsnew file+16
@@ -0,0 +1,16 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Test.Util.ChromeFlags where
3+
4+import Data.Text
5+import System.IO.Temp
6+
7+withSandboxedChromeFlags :: Bool -> ([Text] -> IO a) -> IO a
8+withSandboxedChromeFlags headless action =
9+ withSystemTempDirectory "reflex-dom-core_test" $ \tmp ->
10+ action
11+ [ if headless then "--headless" else "--auto-open-devtools-for-tabs"
12+ , "--disable-gpu"
13+ , "--no-sandbox"
14+ , "--remote-debugging-port=9222"
15+ , "--user-data-dir=" <> pack tmp
16+ ]
chrome-test-utils/src/Test/Util/UnshareNetwork.hsnew file+12
@@ -0,0 +1,12 @@
1+module Test.Util.UnshareNetwork where
2+
3+import System.Linux.Namespaces
4+import System.Posix
5+import System.Process
6+
7+unshareNetork :: IO ()
8+unshareNetork = do
9+ uid <- getEffectiveUserID
10+ unshare [User, Network]
11+ writeUserMappings Nothing [UserMapping 0 uid 1]
12+ callCommand "ip link set lo up ; ip addr"
default.nix+1
@@ -3,4 +3,5 @@ haskellPackages: nixpkgs: {
33 reflex-dom-core = haskellPackages.callPackage ./reflex-dom-core {
44 inherit (nixpkgs) iproute chromium;
55 };
6+ chrome-test-utils = haskellPackages.callCabal2nix "chrome-test-utils" ./chrome-test-utils {};
67}
reflex-dom-core/default.nix+24−13
@@ -1,18 +1,21 @@
1−{ mkDerivation, ghc, aeson, base, bifunctors, bimap, blaze-builder
2−, bytestring, constraints, containers, contravariant, data-default
3−, dependent-map, dependent-sum, dependent-sum-template, directory
4−, exception-transformers, ghcjs-dom, hlint, jsaddle, keycode, lens
5−, monad-control, mtl, primitive, ref-tf, reflex, semigroups, stdenv
6−, stm, template-haskell, temporary, text, these, transformers
7−, unix, zenc, hashable, chromium, process, jsaddle-warp
8−, linux-namespaces, iproute, network-uri, fontconfig
1+{ mkDerivation, aeson, base, bifunctors, bimap, blaze-builder
2+, bytestring, chrome-test-utils, constraints, constraints-extras, containers
3+, contravariant, data-default, dependent-map, dependent-sum
4+, dependent-sum-template, directory, exception-transformers
5+, exceptions, filepath, ghcjs-dom, hlint, hspec, hspec-webdriver, http-types, HUnit
6+, jsaddle, jsaddle-warp, keycode, lens, lifted-base
7+, monad-control, mtl, network, network-uri, primitive, process
8+, random, ref-tf, reflex, semigroups, silently, stdenv, stm
9+, template-haskell, temporary, text, these, transformers, unix, wai
10+, wai-websockets, warp, webdriver, websockets, zenc
11+, ghc, iproute, chromium, hashable, selenium-server-standalone, which, fontconfig
912}:
1013let addGcTestDepends = drv: if (stdenv.system != "x86_64-linux" || stdenv.hostPlatform != stdenv.buildPlatform || (ghc.isGhcjs or false)) then drv else drv // {
1114 # The headless browser run as part of gc tests would hang/crash without this
1215 preCheck = ''
1316 export FONTCONFIG_PATH=${fontconfig.out}/etc/fonts
1417 '';
15− testHaskellDepends = (drv.testHaskellDepends or []) ++ [ temporary jsaddle-warp process linux-namespaces ];
18+ testHaskellDepends = (drv.testHaskellDepends or []) ++ [ temporary jsaddle-warp process ];
1619 testSystemDepends = (drv.testSystemDepends or []) ++ [ chromium iproute ];
1720 };
1821in mkDerivation (addGcTestDepends {
@@ -23,9 +26,9 @@ in mkDerivation (addGcTestDepends {
2326 aeson base bifunctors bimap blaze-builder bytestring constraints
2427 containers contravariant data-default dependent-map dependent-sum
2528 dependent-sum-template directory exception-transformers ghcjs-dom
26− jsaddle keycode lens monad-control mtl primitive ref-tf
27− reflex semigroups stm template-haskell text these transformers
28− unix zenc network-uri
29+ jsaddle keycode lens monad-control mtl network-uri primitive random
30+ ref-tf reflex semigroups stm template-haskell text these
31+ transformers unix zenc
2932 ] ++ (if ghc.isGhcjs or false then [
3033 hashable
3134 ] else []);
@@ -41,7 +44,15 @@ in mkDerivation (addGcTestDepends {
4144 # Show some output while running tests, so we might notice what's wrong
4245 testTarget = "--show-details=streaming";
4346
44− testHaskellDepends = [ base hlint ];
47+ testSystemDepends = [ selenium-server-standalone which ];
48+ testHaskellDepends = [
49+ aeson base bytestring chrome-test-utils constraints constraints-extras containers
50+ dependent-map dependent-sum dependent-sum-template directory
51+ exceptions filepath ghcjs-dom hlint hspec hspec-webdriver http-types HUnit jsaddle
52+ jsaddle-warp lens lifted-base network process random reflex
53+ silently temporary text unix wai wai-websockets warp webdriver
54+ websockets
55+ ];
4556 description = "Functional Reactive Web Apps with Reflex";
4657 license = stdenv.lib.licenses.bsd3;
4758})
reflex-dom-core/reflex-dom-core.cabal+51−5
@@ -64,6 +64,7 @@ library
6464 monad-control >= 1.0.1 && < 1.1,
6565 mtl >= 2.1 && < 2.3,
6666 primitive >= 0.5 && < 0.7,
67+ random,
6768 ref-tf == 0.4.*,
6869 reflex == 0.5.*,
6970 semigroups >= 0.16 && < 0.19,
@@ -100,7 +101,6 @@ library
100101 Reflex.Dom.Modals.Class
101102 Reflex.Dom.Old
102103 Reflex.Dom.Prerender
103− Reflex.Dom.Specializations
104104 Reflex.Dom.Time
105105 Reflex.Dom.WebSocket
106106 Reflex.Dom.WebSocket.Query
@@ -149,16 +149,62 @@ test-suite hlint
149149 main-is: hlint.hs
150150 type: exitcode-stdio-1.0
151151
152−test-suite gc
152+test-suite hydration
153153 build-depends: base
154+ , aeson
155+ , bytestring
156+ , chrome-test-utils
157+ , constraints
158+ , constraints-extras
159+ , containers
160+ , dependent-map
161+ , dependent-sum
162+ , dependent-sum-template
163+ , directory
164+ , exceptions
165+ , filepath
166+ , ghcjs-dom
167+ , hspec
168+ , hspec-webdriver
169+ , http-types
170+ , HUnit
171+ , jsaddle
172+ , jsaddle-warp
173+ , lens
174+ , lifted-base
175+ , network
176+ , random
177+ , ref-tf
154178 , reflex
155179 , reflex-dom-core
180+ , process
181+ , silently
182+ , temporary
183+ , text
184+ , wai
185+ , wai-websockets
186+ , warp
187+ , webdriver
188+ , websockets
189+ hs-source-dirs: test
190+ ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
191+ ghc-prof-options: -fprof-auto -optP-DPROFILING
192+ main-is: hydration.hs
193+ type: exitcode-stdio-1.0
194+ -- if !os(linux) || !arch(x86_64)-- || impl(ghc >= 8.4.0)
195+ buildable: False
196+
197+-- broken test on base 4.11 & ghc 8.4.3
198+-- needs to be updated for changes in GHC.Stats
199+test-suite gc
200+ build-depends: base
201+ , chrome-test-utils
156202 , jsaddle
157203 , jsaddle-warp
158− , linux-namespaces
159204 , process
160− , temporary
161− , unix
205+ , reflex
206+ , reflex-dom-core
207+ , text
162208 hs-source-dirs: test
163209 ghc-options: -rtsopts -with-rtsopts=-T -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
164210 ghc-prof-options: -fprof-auto -optP-DPROFILING
reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+52−2
@@ -35,6 +35,7 @@ import Reflex.Dom.Builder.Class.Events
3535#ifdef USE_TEMPLATE_HASKELL
3636import Reflex.Dom.Builder.Class.TH
3737#endif
38+import Reflex.BehaviorWriter.Base
3839import Reflex.DynamicWriter.Base
3940import Reflex.EventWriter.Base
4041import Reflex.NotReady.Class
@@ -61,13 +62,14 @@ import Data.String
6162import Data.Text (Text)
6263import Data.Type.Coercion
6364import GHCJS.DOM.Types (JSM)
65+import qualified GHCJS.DOM.Types as DOM
6466
6567class Default (EventSpec d EventResult) => DomSpace d where
6668 type EventSpec d :: (EventTag -> *) -> *
6769 type RawDocument d :: *
6870 type RawTextNode d :: *
71+ type RawCommentNode d :: *
6972 type RawElement d :: *
70− type RawFile d :: *
7173 type RawInputElement d :: *
7274 type RawTextAreaElement d :: *
7375 type RawSelectElement d :: *
@@ -86,6 +88,15 @@ class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), NotReady t m, Adjustable
8688 => TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
8789 textNode = lift . textNode
8890 {-# INLINABLE textNode #-}
91+ commentNode :: CommentNodeConfig t -> m (CommentNode (DomBuilderSpace m) t)
92+ default commentNode :: ( MonadTrans f
93+ , m ~ f m'
94+ , DomBuilderSpace m' ~ DomBuilderSpace m
95+ , DomBuilder t m'
96+ )
97+ => CommentNodeConfig t -> m (CommentNode (DomBuilderSpace m) t)
98+ commentNode = lift . commentNode
99+ {-# INLINABLE commentNode #-}
89100 element :: Text -> ElementConfig er t (DomBuilderSpace m) -> m a -> m (Element er (DomBuilderSpace m) t, a)
90101 default element :: ( MonadTransControl f
91102 , StT f a ~ a
@@ -175,6 +186,28 @@ newtype TextNode d t = TextNode
175186 { _textNode_raw :: RawTextNode d
176187 }
177188
189+data CommentNodeConfig t
190+ = CommentNodeConfig { _commentNodeConfig_initialContents :: {-# UNPACK #-} !Text
191+ , _commentNodeConfig_setContents :: !(Maybe (Event t Text))
192+ }
193+
194+#ifndef USE_TEMPLATE_HASKELL
195+commentNodeConfig_initialContents :: Lens' (CommentNodeConfig t) Comment
196+commentNodeConfig_initialContents f (CommentNodeConfig a b) = (\a' -> CommentNodeConfig a' b) <$> f a
197+{-# INLINE commentNodeConfig_initialContents #-}
198+#endif
199+
200+instance (Reflex t) => Default (CommentNodeConfig t) where
201+ {-# INLINABLE def #-}
202+ def = CommentNodeConfig
203+ { _commentNodeConfig_initialContents = mempty
204+ , _commentNodeConfig_setContents = Nothing
205+ }
206+
207+newtype CommentNode d t = CommentNode
208+ { _commentNode_raw :: RawCommentNode d
209+ }
210+
178211data AttributeName = AttributeName !(Maybe Namespace) !Text deriving (Show, Read, Eq, Ord)
179212
180213mapKeysToAttributeName :: Map Text v -> Map AttributeName v
@@ -291,7 +324,7 @@ data InputElement er d t
291324 , _inputElement_hasFocus :: Dynamic t Bool
292325 , _inputElement_element :: Element er d t
293326 , _inputElement_raw :: RawInputElement d
294− , _inputElement_files :: Dynamic t [RawFile d]
327+ , _inputElement_files :: Dynamic t [DOM.File]
295328 }
296329
297330data TextAreaElementConfig er t m
@@ -393,6 +426,7 @@ data SelectElement er d t = SelectElement
393426#ifdef USE_TEMPLATE_HASKELL
394427concat <$> mapM (uncurry makeLensesWithoutField)
395428 [ (["_textNodeConfig_setContents"], ''TextNodeConfig)
429+ , (["_commentNodeConfig_setContents"], ''CommentNodeConfig)
396430 , ([ "_inputElementConfig_setValue"
397431 , "_inputElementConfig_setChecked" ], ''InputElementConfig)
398432 , (["_rawElementConfig_modifyAttributes"], ''RawElementConfig)
@@ -409,6 +443,13 @@ textNodeConfig_setContents =
409443 setter t e = t { _textNodeConfig_setContents = Just e }
410444 in lens getter setter
411445
446+-- | This lens is technically illegal. The implementation of 'CommentNodeConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'CommentNodeConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
447+commentNodeConfig_setContents :: Reflex t => Lens (CommentNodeConfig t) (CommentNodeConfig t) (Event t Text) (Event t Text)
448+commentNodeConfig_setContents =
449+ let getter = fromMaybe never . _commentNodeConfig_setContents
450+ setter t e = t { _commentNodeConfig_setContents = Just e }
451+ in lens getter setter
452+
412453-- | This lens is technically illegal. The implementation of 'InputElementConfig' uses a 'Maybe' under the hood for efficiency reasons. However, always interacting with 'InputElementConfig' via lenses will always behave correctly, and if you pattern match on it, you should always treat 'Nothing' as 'never'.
413454inputElementConfig_setValue :: Reflex t => Lens' (InputElementConfig er t m) (Event t Text)
414455inputElementConfig_setValue =
@@ -521,6 +562,7 @@ instance (MountableDomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m)
521562instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
522563 type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m
523564 textNode = liftTextNode
565+ commentNode = liftCommentNode
524566 element elementTag cfg (DynamicWriterT child) = DynamicWriterT $ do
525567 s <- get
526568 (el, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
@@ -539,6 +581,7 @@ instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (
539581instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT t request response m) where
540582 type DomBuilderSpace (RequesterT t request response m) = DomBuilderSpace m
541583 textNode = liftTextNode
584+ commentNode = liftCommentNode
542585 element elementTag cfg (RequesterT child) = RequesterT $ do
543586 r <- ask
544587 old <- get
@@ -559,6 +602,7 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequesterT
559602instance (DomBuilder t m, MonadHold t m, MonadFix m, Semigroup w) => DomBuilder t (EventWriterT t w m) where
560603 type DomBuilderSpace (EventWriterT t w m) = DomBuilderSpace m
561604 textNode = liftTextNode
605+ commentNode = liftCommentNode
562606 element elementTag cfg (EventWriterT child) = EventWriterT $ do
563607 old <- get
564608 (el, (a, new)) <- lift $ element elementTag cfg $ runStateT child old
@@ -577,6 +621,7 @@ instance (DomBuilder t m, MonadHold t m, MonadFix m, Semigroup w) => DomBuilder
577621instance (DomBuilder t m, MonadFix m, MonadHold t m, Group q, Query q, Additive q) => DomBuilder t (QueryT t q m) where
578622 type DomBuilderSpace (QueryT t q m) = DomBuilderSpace m
579623 textNode = liftTextNode
624+ commentNode = liftCommentNode
580625 element elementTag cfg (QueryT child) = QueryT $ do
581626 s <- get
582627 (e, (a, newS)) <- lift $ element elementTag cfg $ runStateT child s
@@ -645,6 +690,9 @@ liftWithStateless a = liftWith $ \run -> a $ fmap (fromStT (Proxy :: Proxy t)) .
645690liftTextNode :: (MonadTrans f, DomBuilder t m) => TextNodeConfig t -> f m (TextNode (DomBuilderSpace m) t)
646691liftTextNode = lift . textNode
647692
693+liftCommentNode :: (MonadTrans f, DomBuilder t m) => CommentNodeConfig t -> f m (CommentNode (DomBuilderSpace m) t)
694+liftCommentNode = lift . commentNode
695+
648696liftElement :: LiftDomBuilder t f m => Text -> ElementConfig er t (DomBuilderSpace m) -> f m a -> f m (Element er (DomBuilderSpace m) t, a)
649697liftElement elementTag cfg child = liftWithStateless $ \run -> element elementTag cfg $ run child
650698
@@ -668,7 +716,9 @@ instance DomRenderHook t m => DomRenderHook t (Lazy.StateT e m) where
668716 requestDomAction = lift . requestDomAction
669717 requestDomAction_ = lift . requestDomAction_
670718
719+deriving instance DomRenderHook t m => DomRenderHook t (BehaviorWriterT t w m)
671720deriving instance DomRenderHook t m => DomRenderHook t (EventWriterT t w m)
721+deriving instance DomRenderHook t m => DomRenderHook t (DynamicWriterT t w m)
672722deriving instance DomRenderHook t m => DomRenderHook t (RequesterT t req rsp m)
673723deriving instance DomRenderHook t m => DomRenderHook t (PostBuildT t m)
674724deriving instance DomRenderHook t m => DomRenderHook t (QueryT t q m)
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+1638−555
@@ -2,7 +2,8 @@
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE ConstraintKinds #-}
44{-# LANGUAGE DataKinds #-}
5−{-# LANGUAGE DefaultSignatures #-}
5+{-# LANGUAGE DeriveFunctor #-}
6+{-# LANGUAGE DeriveTraversable #-}
67{-# LANGUAGE EmptyDataDecls #-}
78{-# LANGUAGE FlexibleContexts #-}
89{-# LANGUAGE FlexibleInstances #-}
@@ -11,14 +12,15 @@
1112{-# LANGUAGE LambdaCase #-}
1213{-# LANGUAGE MultiParamTypeClasses #-}
1314{-# LANGUAGE OverloadedStrings #-}
15+{-# LANGUAGE PatternGuards #-}
1416{-# LANGUAGE PolyKinds #-}
1517{-# LANGUAGE RankNTypes #-}
1618{-# LANGUAGE RecursiveDo #-}
1719{-# LANGUAGE ScopedTypeVariables #-}
18−{-# LANGUAGE StandaloneDeriving #-}
1920#ifdef USE_TEMPLATE_HASKELL
2021{-# LANGUAGE TemplateHaskell #-}
2122#endif
23+{-# LANGUAGE TupleSections #-}
2224{-# LANGUAGE TypeFamilies #-}
2325{-# LANGUAGE TypeOperators #-}
2426{-# LANGUAGE UndecidableInstances #-}
@@ -26,184 +28,344 @@
2628{-# LANGUAGE ForeignFunctionInterface #-}
2729{-# LANGUAGE JavaScriptFFI #-}
2830#endif
31+-- | This is a builder to be used on the client side. It can be run in two modes:
32+--
33+-- 1. in "hydration mode", reusing DOM nodes already in the page (as produced
34+-- by 'Reflex.Dom.Builder.Static.renderStatic')
35+-- 2. in "immediate mode", creating and appending DOM nodes as required
36+--
37+-- In "hydration mode", the preexisting DOM __must contain__ what the builder
38+-- will expect at switchover time (the time at which parity with the static
39+-- renderer is reached, and the time after which the page is "live").
40+--
41+-- For example, displaying the current time as text should be done inside
42+-- 'Reflex.Dom.Prerender.prerender' to ensure that we don't attempt to hydrate the incorrect text.
43+-- The server will prerender a text node with time A, and the client will expect
44+-- a text node with time B. Barring a miracle, time A and time B will not match,
45+-- and hydration will fail.
2946module Reflex.Dom.Builder.Immediate
30− ( EventTriggerRef (..)
31− , ImmediateDomBuilderEnv (..)
32− , ImmediateDomBuilderT (..)
33− , runImmediateDomBuilderT
34− , askParent
35− , askEvents
36− , append
37− , textNodeInternal
38− , deleteBetweenExclusive
39− , extractBetweenExclusive
40− , deleteUpTo
41− , extractUpTo
42− , SupportsImmediateDomBuilder
43− , collectUpTo
44− , collectUpToGivenParent
45− , EventFilterTriggerRef (..)
46− , wrap
47− , makeElement
48− , GhcjsDomHandler (..)
49− , GhcjsDomHandler1 (..)
50− , GhcjsDomEvent (..)
51− , GhcjsDomSpace
52− , GhcjsEventFilter (..)
53− , Pair1 (..)
54− , Maybe1 (..)
55− , GhcjsEventSpec (..)
56− , HasDocument (..)
57− , ghcjsEventSpec_filters
58− , ghcjsEventSpec_handler
59− , GhcjsEventHandler (..)
47+ ( HydrationDomBuilderT (..)
48+ , HydrationDomBuilderEnv (..)
49+ , HydrationMode (..)
50+ , HydrationRunnerT (..)
51+ , runHydrationRunnerT
52+ , ImmediateDomBuilderT
53+ , runHydrationDomBuilderT
54+ , getHydrationMode
55+ , addHydrationStep
56+ , addHydrationStepWithSetup
57+ , setPreviousNode
58+ , insertAfterPreviousNode
59+ , hydrateComment
60+ , askParent
61+ , askEvents
62+ , append
63+ , textNodeInternal
64+ , removeSubsequentNodes
65+ , deleteBetweenExclusive
66+ , extractBetweenExclusive
67+ , deleteUpTo
68+ , extractUpTo
69+ , SupportsHydrationDomBuilder
70+ , collectUpTo
71+ , collectUpToGivenParent
72+ , EventTriggerRef (..)
73+ , EventFilterTriggerRef (..)
74+ , wrap
75+ , elementInternal
76+ , HydrationDomSpace
77+ , GhcjsDomSpace
78+ , GhcjsDomHandler (..)
79+ , GhcjsDomHandler1 (..)
80+ , GhcjsDomEvent (..)
81+ , GhcjsEventFilter (..)
82+ , Pair1 (..)
83+ , Maybe1 (..)
84+ , GhcjsEventSpec (..)
85+ , HasDocument (..)
86+ , ghcjsEventSpec_filters
87+ , ghcjsEventSpec_handler
88+ , GhcjsEventHandler (..)
6089#ifndef USE_TEMPLATE_HASKELL
61− , phantom2
90+ , phantom2
6291#endif
63− , drawChildUpdate
64− , ChildReadyState (..)
65− , ChildReadyStateInt (..)
66− , mkHasFocus
67− , insertBefore
68− , EventType
69− , defaultDomEventHandler
70− , defaultDomWindowEventHandler
71− , withIsEvent
72− , showEventName
73− , elementOnEventName
74− , windowOnEventName
75− , wrapDomEvent
76− , subscribeDomEvent
77− , wrapDomEventMaybe
78− , wrapDomEventsMaybe
79− , getKeyEvent
80− , getMouseEventCoords
81− , getTouchEvent
82− , WindowConfig (..)
83− , Window (..)
84− , wrapWindow
85− -- * Internal
86− , traverseDMapWithKeyWithAdjust'
87− , hoistTraverseWithKeyWithAdjust
88− , traverseIntMapWithKeyWithAdjust'
89− , hoistTraverseIntMapWithKeyWithAdjust
90− ) where
91−
92−import Foreign.JavaScript.TH
93−import Reflex.Adjustable.Class
94−import Reflex.Class as Reflex
95−import Reflex.Dom.Builder.Class
96−import Reflex.Dynamic
97−import Reflex.Host.Class
98−import qualified Reflex.Patch.DMap as PatchDMap
99−import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
100−import Reflex.PerformEvent.Class
101−import Reflex.PostBuild.Class
102−import Reflex.TriggerEvent.Base hiding (askEvents)
103−import qualified Reflex.TriggerEvent.Base as TriggerEventT (askEvents)
104−import Reflex.TriggerEvent.Class
92+ , drawChildUpdate
93+ , ChildReadyState (..)
94+ , mkHasFocus
95+ , insertBefore
96+ , EventType
97+ , defaultDomEventHandler
98+ , defaultDomWindowEventHandler
99+ , withIsEvent
100+ , showEventName
101+ , elementOnEventName
102+ , windowOnEventName
103+ , wrapDomEvent
104+ , subscribeDomEvent
105+ , wrapDomEventMaybe
106+ , wrapDomEventsMaybe
107+ , getKeyEvent
108+ , getMouseEventCoords
109+ , getTouchEvent
110+ , WindowConfig (..)
111+ , Window (..)
112+ , wrapWindow
113+ -- * Internal
114+ , traverseDMapWithKeyWithAdjust'
115+ , hoistTraverseWithKeyWithAdjust
116+ , traverseIntMapWithKeyWithAdjust'
117+ , hoistTraverseIntMapWithKeyWithAdjust
118+ ) where
105119
106120import Control.Concurrent
107−import Control.Lens hiding (element, ix)
121+import Control.Exception (bracketOnError)
122+import Control.Lens (Identity(..), imapM_, iforM_, (^.), makeLenses)
108123import Control.Monad.Exception
109124import Control.Monad.Primitive
110125import Control.Monad.Reader
111126import Control.Monad.Ref
112−#ifndef USE_TEMPLATE_HASKELL
113−import Data.Functor.Contravariant (phantom)
114−#endif
127+import Control.Monad.State.Strict (StateT, mapStateT, get, modify', gets, runStateT)
115128import Data.Bitraversable
116129import Data.Default
117130import Data.Dependent.Map (DMap)
118−import qualified Data.Dependent.Map as DMap
119131import Data.Dependent.Sum
132+import Data.FastMutableIntMap (PatchIntMap (..))
133+import Data.Foldable (for_, traverse_)
120134import Data.Functor.Compose
121135import Data.Functor.Constant
122136import Data.Functor.Misc
123137import Data.Functor.Product
124138import Data.IORef
125−import qualified Data.Map as Map
139+import Data.IntMap.Strict (IntMap)
126140import Data.Maybe
127−import Data.Monoid hiding (Product)
128−import Data.Some (Some)
129−import qualified Data.Some as Some
141+import Data.Some (Some(..))
130142import Data.Text (Text)
143+import Foreign.JavaScript.Internal.Utils
144+import Foreign.JavaScript.TH
145+import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
146+import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute, hasAttributeNS)
147+import GHCJS.DOM.EventM (EventM, event, on)
148+import GHCJS.DOM.KeyboardEvent as KeyboardEvent
149+import GHCJS.DOM.MouseEvent
150+import GHCJS.DOM.Node (appendChild_, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
151+import GHCJS.DOM.Types (liftJSM, askJSM, runJSM, JSM, MonadJSM, FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node, TouchEvent, WheelEvent, uncheckedCastTo, ClipboardEvent)
152+import GHCJS.DOM.UIEvent
153+import Language.Javascript.JSaddle (call, eval)
154+import Reflex.Adjustable.Class
155+import Reflex.Class as Reflex
156+import Reflex.Dom.Builder.Class
157+import Reflex.Dynamic
158+import Reflex.Host.Class
159+import Reflex.Patch.DMapWithMove (PatchDMapWithMove(..))
160+import Reflex.Patch.MapWithMove (PatchMapWithMove(..))
161+import Reflex.PerformEvent.Base (PerformEventT)
162+import Reflex.PerformEvent.Class
163+import Reflex.PostBuild.Base (PostBuildT)
164+import Reflex.PostBuild.Class
165+import Reflex.Requester.Base
166+import Reflex.Requester.Class
167+import Reflex.Spider (Spider, SpiderHost, Global)
168+import Reflex.TriggerEvent.Base hiding (askEvents)
169+import Reflex.TriggerEvent.Class
170+
171+import qualified Data.Dependent.Map as DMap
172+import qualified Data.FastMutableIntMap as FastMutableIntMap
173+import qualified Data.IntMap.Strict as IntMap
174+import qualified Data.Map as Map
175+import qualified Data.Text as T
131176import qualified GHCJS.DOM as DOM
132−import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode)
133−import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
177+import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
178+import qualified GHCJS.DOM.DocumentOrShadowRoot as Document
134179import qualified GHCJS.DOM.Element as Element
135180import qualified GHCJS.DOM.Event as Event
136−import qualified GHCJS.DOM.GlobalEventHandlers as Events
137−import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
138−import GHCJS.DOM.EventM (EventM, event, on)
139181import qualified GHCJS.DOM.EventM as DOM
140182import qualified GHCJS.DOM.FileList as FileList
183+import qualified GHCJS.DOM.GlobalEventHandlers as Events
141184import qualified GHCJS.DOM.HTMLInputElement as Input
142185import qualified GHCJS.DOM.HTMLSelectElement as Select
143186import qualified GHCJS.DOM.HTMLTextAreaElement as TextArea
144−import GHCJS.DOM.MouseEvent
187+import qualified GHCJS.DOM.Node as Node
188+import qualified GHCJS.DOM.Text as DOM
145189import qualified GHCJS.DOM.Touch as Touch
146190import qualified GHCJS.DOM.TouchEvent as TouchEvent
147191import qualified GHCJS.DOM.TouchList as TouchList
148−import GHCJS.DOM.Node (appendChild_, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
149−import qualified GHCJS.DOM.Node as DOM (insertBefore_)
150−import GHCJS.DOM.Types
151− (liftJSM, askJSM, runJSM, JSM, MonadJSM,
152− FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
153− ToDOMString, TouchEvent, WheelEvent, uncheckedCastTo, ClipboardEvent)
154192import qualified GHCJS.DOM.Types as DOM
155−import GHCJS.DOM.UIEvent
156−import GHCJS.DOM.KeyboardEvent as KeyboardEvent
157193import qualified GHCJS.DOM.Window as Window
158−import Language.Javascript.JSaddle (call, eval)
159−import Data.IntMap.Strict (IntMap)
160−import qualified Data.IntMap.Strict as IntMap
161−import Data.FastMutableIntMap (PatchIntMap (..))
162−import qualified Data.FastMutableIntMap as FastMutableIntMap
194+import qualified Reflex.Patch.DMap as PatchDMap
195+import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
196+import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
197+import qualified Reflex.TriggerEvent.Base as TriggerEventT (askEvents)
163198
164−import Reflex.Requester.Base
165−import Reflex.Requester.Class
166−import Foreign.JavaScript.Internal.Utils
199+#ifndef USE_TEMPLATE_HASKELL
200+import Data.Functor.Contravariant (phantom)
201+#endif
167202
168203#ifndef ghcjs_HOST_OS
169204import GHCJS.DOM.Types (MonadJSM (..))
170205
171−instance MonadJSM m => MonadJSM (ImmediateDomBuilderT t m) where
172− liftJSM' = ImmediateDomBuilderT . liftJSM'
206+instance MonadJSM m => MonadJSM (HydrationRunnerT t m) where
207+ {-# INLINABLE liftJSM' #-}
208+ liftJSM' = lift . liftJSM'
209+
210+instance MonadJSM m => MonadJSM (HydrationDomBuilderT s t m) where
211+ {-# INLINABLE liftJSM' #-}
212+ liftJSM' = lift . liftJSM'
213+
214+instance MonadJSM m => MonadJSM (DomRenderHookT t m) where
215+ {-# INLINABLE liftJSM' #-}
216+ liftJSM' = lift . liftJSM'
173217#endif
174218
175−data ImmediateDomBuilderEnv t
176− = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
177− , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
178− , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
179− , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
180− }
219+data HydrationDomBuilderEnv t m = HydrationDomBuilderEnv
220+ { _hydrationDomBuilderEnv_document :: {-# UNPACK #-} !Document
221+ -- ^ Reference to the document
222+ , _hydrationDomBuilderEnv_parent :: !(Either Node (IORef Node))
223+ -- ^ This is in an IORef because in the time up to hydration we can't actually know what the
224+ -- parent is - we populate this reference during the DOM traversal at hydration time
225+ , _hydrationDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word)
226+ -- ^ Number of children who still aren't fully rendered
227+ , _hydrationDomBuilderEnv_commitAction :: !(JSM ())
228+ -- ^ Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
229+ , _hydrationDomBuilderEnv_hydrationMode :: {-# UNPACK #-} !(IORef HydrationMode)
230+ -- ^ In hydration mode? Should be switched to `HydrationMode_Immediate` after hydration is finished
231+ , _hydrationDomBuilderEnv_switchover :: !(Event t ())
232+ , _hydrationDomBuilderEnv_delayed :: {-# UNPACK #-} !(IORef (HydrationRunnerT t m ()))
233+ }
181234
182−newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a }
235+-- | A monad for DomBuilder which just gets the results of children and pushes
236+-- work into an action that is delayed until after postBuild (to match the
237+-- static builder). The action runs in 'HydrationRunnerT', which performs the
238+-- DOM takeover and sets up the events, after which point this monad will
239+-- continue in the vein of 'ImmediateDomBuilderT'.
240+newtype HydrationDomBuilderT s t m a = HydrationDomBuilderT { unHydrationDomBuilderT :: ReaderT (HydrationDomBuilderEnv t m) (DomRenderHookT t m) a }
183241 deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
184242#if MIN_VERSION_base(4,9,1)
185243 , MonadAsyncException
186244#endif
187245 )
188246
189−instance PrimMonad m => PrimMonad (ImmediateDomBuilderT x m) where
190− type PrimState (ImmediateDomBuilderT x m) = PrimState m
247+instance PrimMonad m => PrimMonad (HydrationDomBuilderT s t m) where
248+ type PrimState (HydrationDomBuilderT s t m) = PrimState m
191249 primitive = lift . primitive
192250
193−instance MonadTrans (ImmediateDomBuilderT t) where
194− lift = ImmediateDomBuilderT . lift . lift . lift
251+instance MonadTrans (HydrationDomBuilderT s t) where
252+ lift = HydrationDomBuilderT . lift . lift
253+
254+instance (Reflex t, MonadFix m) => DomRenderHook t (HydrationDomBuilderT s t m) where
255+ withRenderHook hook = HydrationDomBuilderT . mapReaderT (withRenderHook hook) . unHydrationDomBuilderT
256+ requestDomAction = HydrationDomBuilderT . lift . requestDomAction
257+ requestDomAction_ = HydrationDomBuilderT . lift . requestDomAction_
195258
196−instance (Reflex t, MonadFix m) => DomRenderHook t (ImmediateDomBuilderT t m) where
197− withRenderHook hook (ImmediateDomBuilderT a) = do
198− e <- ImmediateDomBuilderT ask
199− ImmediateDomBuilderT $ lift $ withRequesting $ \rsp -> do
200− (x, req) <- lift $ runRequesterT (runReaderT a e) $ runIdentity <$> rsp
259+-- | The monad which performs the delayed actions to reuse prerendered nodes and set up events.
260+-- State contains reference to the previous node sibling, if any, and the reader contains reference to the parent node.
261+newtype HydrationRunnerT t m a = HydrationRunnerT { unHydrationRunnerT :: StateT HydrationState (ReaderT Node (DomRenderHookT t m)) a }
262+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
263+#if MIN_VERSION_base(4,9,1)
264+ , MonadAsyncException
265+#endif
266+ )
267+
268+data HydrationState = HydrationState
269+ { _hydrationState_previousNode :: !(Maybe Node)
270+ , _hydrationState_failed :: !Bool
271+ }
272+
273+{-# INLINABLE localRunner #-}
274+localRunner :: (MonadJSM m, Monad m) => HydrationRunnerT t m a -> Maybe Node -> Node -> HydrationRunnerT t m a
275+localRunner (HydrationRunnerT m) s parent = do
276+ s0 <- HydrationRunnerT get
277+ (a, s') <- HydrationRunnerT $ lift $ local (\_ -> parent) $ runStateT m (s0 { _hydrationState_previousNode = s })
278+ traverse_ removeSubsequentNodes $ _hydrationState_previousNode s'
279+ HydrationRunnerT $ modify' $ \hs -> hs { _hydrationState_failed = _hydrationState_failed s' }
280+ pure a
281+
282+{-# INLINABLE runHydrationRunnerT #-}
283+runHydrationRunnerT
284+ :: (MonadRef m, Ref m ~ IORef, Monad m, PerformEvent t m, MonadFix m, MonadReflexCreateTrigger t m, MonadJSM m, MonadJSM (Performable m))
285+ => HydrationRunnerT t m a -> Maybe Node -> Node -> Chan [DSum (EventTriggerRef t) TriggerInvocation] -> m a
286+runHydrationRunnerT (HydrationRunnerT m) s parent events = flip runDomRenderHookT events $ flip runReaderT parent $ do
287+ (a, s') <- runStateT m (HydrationState s False)
288+ traverse_ removeSubsequentNodes $ _hydrationState_previousNode s'
289+ when (_hydrationState_failed s') $ liftIO $ putStrLn "reflex-dom warning: hydration failed: the DOM was not as expected at switchover time. This may be due to invalid HTML which the browser has altered upon parsing, some external JS altering the DOM, or the page being served from an outdated cache."
290+ pure a
291+
292+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydrationRunnerT t m) where
293+ {-# INLINABLE newEventWithTrigger #-}
294+ newEventWithTrigger = lift . newEventWithTrigger
295+ {-# INLINABLE newFanEventWithTrigger #-}
296+ newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
297+
298+instance MonadTrans (HydrationRunnerT t) where
299+ {-# INLINABLE lift #-}
300+ lift = HydrationRunnerT . lift . lift . lift
301+
302+instance MonadSample t m => MonadSample t (HydrationRunnerT t m) where
303+ {-# INLINABLE sample #-}
304+ sample = lift . sample
305+
306+instance (Reflex t, MonadFix m) => DomRenderHook t (HydrationRunnerT t m) where
307+ withRenderHook hook = HydrationRunnerT . mapStateT (mapReaderT (withRenderHook hook)) . unHydrationRunnerT
308+ requestDomAction = HydrationRunnerT . lift . lift . requestDomAction
309+ requestDomAction_ = HydrationRunnerT . lift . lift . requestDomAction_
310+
311+-- | Add a hydration step which depends on some computation that should only be
312+-- done *before* the switchover to immediate mode - this is most likely some
313+-- form of 'hold' which we want to remove after hydration is done
314+{-# INLINABLE addHydrationStepWithSetup #-}
315+addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> HydrationRunnerT t m ()) -> HydrationDomBuilderT s t m ()
316+addHydrationStepWithSetup setup f = getHydrationMode >>= \case
317+ HydrationMode_Immediate -> pure ()
318+ HydrationMode_Hydrating -> do
319+ switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
320+ (s, _) <- lift $ runWithReplace setup $ return () <$ switchover
321+ addHydrationStep (f s)
322+
323+-- | Add a hydration step
324+{-# INLINABLE addHydrationStep #-}
325+addHydrationStep :: MonadIO m => HydrationRunnerT t m () -> HydrationDomBuilderT s t m ()
326+addHydrationStep m = do
327+ delayedRef <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_delayed
328+ liftIO $ modifyIORef' delayedRef (>> m)
329+
330+-- | Shared behavior for HydrationDomBuilderT and HydrationRunnerT
331+newtype DomRenderHookT t m a = DomRenderHookT { unDomRenderHookT :: RequesterT t JSM Identity (TriggerEventT t m) a }
332+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
333+#if MIN_VERSION_base(4,9,1)
334+ , MonadAsyncException
335+#endif
336+ )
337+
338+{-# INLINABLE runDomRenderHookT #-}
339+runDomRenderHookT
340+ :: (MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadJSM m, MonadJSM (Performable m), MonadRef m, Ref m ~ IORef)
341+ => DomRenderHookT t m a
342+ -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
343+ -> m a
344+runDomRenderHookT (DomRenderHookT a) events = do
345+ flip runTriggerEventT events $ do
346+ rec (result, req) <- runRequesterT a rsp
347+ rsp <- performEventAsync $ ffor req $ \rm f -> liftJSM $ runInAnimationFrame f $
348+ traverseRequesterData (\r -> Identity <$> r) rm
349+ return result
350+ where
351+ runInAnimationFrame f x = void . DOM.inAnimationFrame' $ \_ -> do
352+ v <- synchronously x
353+ void . liftIO $ f v
354+
355+instance MonadTrans (DomRenderHookT t) where
356+ {-# INLINABLE lift #-}
357+ lift = DomRenderHookT . lift . lift
358+
359+instance (Reflex t, MonadFix m) => DomRenderHook t (DomRenderHookT t m) where
360+ withRenderHook hook (DomRenderHookT a) = do
361+ DomRenderHookT $ withRequesting $ \rsp -> do
362+ (x, req) <- lift $ runRequesterT a $ runIdentity <$> rsp
201363 return (ffor req $ \rm -> hook $ traverseRequesterData (\r -> Identity <$> r) rm, x)
202− requestDomAction = ImmediateDomBuilderT . lift . requestingIdentity
203− requestDomAction_ = ImmediateDomBuilderT . lift . requesting_
364+ requestDomAction = DomRenderHookT . requestingIdentity
365+ requestDomAction_ = DomRenderHookT . requesting_
204366
205−{-# INLINABLE runImmediateDomBuilderT #-}
206−runImmediateDomBuilderT
367+{-# INLINABLE runHydrationDomBuilderT #-}
368+runHydrationDomBuilderT
207369 :: ( MonadFix m
208370 , PerformEvent t m
209371 , MonadReflexCreateTrigger t m
@@ -212,50 +374,76 @@ runImmediateDomBuilderT
212374 , MonadRef m
213375 , Ref m ~ IORef
214376 )
215− => ImmediateDomBuilderT t m a
216− -> ImmediateDomBuilderEnv t
377+ => HydrationDomBuilderT s t m a
378+ -> HydrationDomBuilderEnv t m
217379 -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
218380 -> m a
219−runImmediateDomBuilderT (ImmediateDomBuilderT a) env eventChan =
220− flip runTriggerEventT eventChan $ do
221− rec (x, req) <- runRequesterT (runReaderT a env) rsp
222− rsp <- performEventAsync $ ffor req $ \rm f -> liftJSM $ runInAnimationFrame f $
223− traverseRequesterData (\r -> Identity <$> r) rm
224− return x
225− where
226− runInAnimationFrame f x = void . DOM.inAnimationFrame' $ \_ -> do
227− v <- synchronously x
228− void . liftIO $ f v
381+runHydrationDomBuilderT (HydrationDomBuilderT a) env = runDomRenderHookT (runReaderT a env)
229382
230−instance Monad m => HasDocument (ImmediateDomBuilderT t m) where
383+instance (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, Monad m) => HasDocument (HydrationDomBuilderT s t m) where
231384 {-# INLINABLE askDocument #-}
232− askDocument = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_document
385+ askDocument = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_document
233386
234387{-# INLINABLE askParent #-}
235−askParent :: Monad m => ImmediateDomBuilderT t m Node
236−askParent = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_parent
388+askParent :: Monad m => HydrationRunnerT t m DOM.Node
389+askParent = HydrationRunnerT ask
390+
391+{-# INLINABLE getParent #-}
392+getParent :: MonadIO m => HydrationDomBuilderT s t m DOM.Node
393+getParent = either pure (liftIO . readIORef) =<< HydrationDomBuilderT (asks _hydrationDomBuilderEnv_parent)
237394
238395{-# INLINABLE askEvents #-}
239−askEvents :: Monad m => ImmediateDomBuilderT t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
240−askEvents = ImmediateDomBuilderT . lift . lift $ TriggerEventT.askEvents
396+askEvents :: Monad m => HydrationDomBuilderT s t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
397+askEvents = HydrationDomBuilderT . lift . DomRenderHookT . lift $ TriggerEventT.askEvents
241398
242−localEnv :: Monad m => (ImmediateDomBuilderEnv t -> ImmediateDomBuilderEnv t) -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m a
243−localEnv f = ImmediateDomBuilderT . local f . unImmediateDomBuilderT
399+{-# INLINABLE localEnv #-}
400+localEnv :: Monad m => (HydrationDomBuilderEnv t m -> HydrationDomBuilderEnv t m) -> HydrationDomBuilderT s t m a -> HydrationDomBuilderT s t m a
401+localEnv f = HydrationDomBuilderT . local (f $!) . unHydrationDomBuilderT
244402
245403{-# INLINABLE append #-}
246−append :: MonadJSM m => DOM.Node -> ImmediateDomBuilderT t m ()
404+append :: MonadJSM m => DOM.Node -> HydrationDomBuilderT s t m ()
247405append n = do
248− p <- askParent
406+ p <- getParent
249407 liftJSM $ appendChild_ p n
250408 return ()
251409
252−{-# INLINABLE textNodeInternal #-}
253−textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
254−textNodeInternal !t = do
255− doc <- askDocument
256− n <- liftJSM $ createTextNode doc t
257− append $ toNode n
258− return n
410+{-# SPECIALIZE append
411+ :: DOM.Node
412+ -> HydrationDomBuilderT s Spider HydrationM ()
413+ #-}
414+
415+data HydrationMode
416+ = HydrationMode_Hydrating
417+ -- ^ The time from initial load to parity with static builder
418+ | HydrationMode_Immediate
419+ -- ^ After hydration
420+ deriving (Eq, Ord, Show)
421+
422+{-# INLINABLE getPreviousNode #-}
423+getPreviousNode :: Monad m => HydrationRunnerT t m (Maybe DOM.Node)
424+getPreviousNode = HydrationRunnerT $ gets _hydrationState_previousNode
425+
426+{-# INLINABLE setPreviousNode #-}
427+setPreviousNode :: Monad m => Maybe DOM.Node -> HydrationRunnerT t m ()
428+setPreviousNode n = HydrationRunnerT $ modify' (\hs -> hs { _hydrationState_previousNode = n })
429+
430+{-# INLINABLE askUnreadyChildren #-}
431+askUnreadyChildren :: Monad m => HydrationDomBuilderT s t m (IORef Word)
432+askUnreadyChildren = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_unreadyChildren
433+
434+{-# INLINABLE askCommitAction #-}
435+askCommitAction :: Monad m => HydrationDomBuilderT s t m (JSM ())
436+askCommitAction = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_commitAction
437+
438+{-# INLINABLE getHydrationMode #-}
439+getHydrationMode :: MonadIO m => HydrationDomBuilderT s t m HydrationMode
440+getHydrationMode = liftIO . readIORef =<< HydrationDomBuilderT (asks _hydrationDomBuilderEnv_hydrationMode)
441+
442+-- | Remove all nodes after given node
443+removeSubsequentNodes :: (MonadJSM m, IsNode n) => n -> m ()
444+removeSubsequentNodes n = liftJSM $ do
445+ f <- eval ("(function(n) { while (n.nextSibling) { (n.parentNode).removeChild(n.nextSibling); }; })" :: Text)
446+ void $ call f f [n]
259447
260448-- | s and e must both be children of the same node and s must precede e;
261449-- all nodes between s and e will be removed, but s and e will not be removed
@@ -293,7 +481,7 @@ extractUpTo df s e = liftJSM $ do
293481 void $ call f f (df, s, e)
294482#endif
295483
296−type SupportsImmediateDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, Adjustable t m, PrimMonad m)
484+type SupportsHydrationDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, Adjustable t m, PrimMonad m, PerformEvent t m, MonadJSM (Performable m))
297485
298486{-# INLINABLE collectUpTo #-}
299487collectUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m DOM.DocumentFragment
@@ -311,10 +499,15 @@ collectUpToGivenParent currentParent s e = do
311499
312500newtype EventFilterTriggerRef t er (en :: EventTag) = EventFilterTriggerRef (IORef (Maybe (EventTrigger t (er en))))
313501
314−{-# INLINABLE wrap #-}
315−wrap :: forall m er t. (Reflex t, MonadFix m, MonadJSM m, MonadReflexCreateTrigger t m) => RawElement GhcjsDomSpace -> RawElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m (Element er GhcjsDomSpace t)
316−wrap e cfg = do
317− events <- askEvents
502+-- | This 'wrap' is only partial: it doesn't create the 'EventSelector' itself
503+{-# INLINE wrap #-}
504+wrap
505+ :: forall s m er t. (Reflex t, MonadJSM m, MonadReflexCreateTrigger t m, DomRenderHook t m, EventSpec s ~ GhcjsEventSpec)
506+ => Chan [DSum (EventTriggerRef t) TriggerInvocation]
507+ -> DOM.Element
508+ -> RawElementConfig er t s
509+ -> m (DMap EventName (EventFilterTriggerRef t er))
510+wrap events e cfg = do
318511 forM_ (_rawElementConfig_modifyAttributes cfg) $ \modifyAttrs -> requestDomAction_ $ ffor modifyAttrs $ imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
319512 Nothing -> maybe (removeAttribute e n) (setAttribute e n) mv
320513 Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv
@@ -331,46 +524,74 @@ wrap e cfg = do
331524 mv <- liftJSM k --TODO: Only do this when the event is subscribed
332525 liftIO $ forM_ mv $ \v -> writeChan events [EventTriggerRef triggerRef :=> TriggerInvocation v (return ())]
333526 return $ en :=> EventFilterTriggerRef triggerRef
334− es <- do
335− let h :: GhcjsEventHandler er
336− !h = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
337− ctx <- askJSM
338− newFanEventWithTrigger $ \(WrapArg en) t ->
339− case DMap.lookup en eventTriggerRefs of
340− Just (EventFilterTriggerRef r) -> do
341− writeIORef r $ Just t
342− return $ do
343− writeIORef r Nothing
344− Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
345− evt <- DOM.event
346− mv <- lift $ unGhcjsEventHandler h (en, GhcjsDomEvent evt)
347− case mv of
348− Nothing -> return ()
349− Just v -> liftIO $ do
350− --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
351− ref <- newIORef $ Just t
352− writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
353− return $ Element
354− { _element_events = es
355− , _element_raw = e
356− }
357−
358−{-# INLINABLE makeElement #-}
359−makeElement :: forall er t m a. (MonadJSM m, MonadFix m, MonadReflexCreateTrigger t m, Adjustable t m) => Text -> ElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element)
360−makeElement elementTag cfg child = do
361− doc <- askDocument
362− e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
363− Nothing -> createElement doc elementTag
364− Just ens -> createElementNS doc (Just ens) elementTag
365− ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
366− Nothing -> lift $ setAttribute e n v
367− Just ans -> lift $ setAttributeNS e (Just ans) n v
368− result <- flip localEnv child $ \env -> env
369− { _immediateDomBuilderEnv_parent = toNode e
370− }
371− append $ toNode e
372− wrapped <- wrap e $ extractRawElementConfig cfg
373− return ((wrapped, result), e)
527+ return eventTriggerRefs
528+
529+{-# SPECIALIZE wrap
530+ :: Chan [DSum (EventTriggerRef Spider) TriggerInvocation]
531+ -> DOM.Element
532+ -> RawElementConfig er Spider HydrationDomSpace
533+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM (DMap EventName (EventFilterTriggerRef Spider er))
534+ #-}
535+
536+{-# SPECIALIZE wrap
537+ :: Chan [DSum (EventTriggerRef Spider) TriggerInvocation]
538+ -> DOM.Element
539+ -> RawElementConfig er Spider GhcjsDomSpace
540+ -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM (DMap EventName (EventFilterTriggerRef Spider er))
541+ #-}
542+
543+{-# INLINE triggerBody #-}
544+triggerBody
545+ :: forall s er t x. EventSpec s ~ GhcjsEventSpec
546+ => DOM.JSContextRef
547+ -> RawElementConfig er t s
548+ -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
549+ -> DMap EventName (EventFilterTriggerRef t er)
550+ -> DOM.Element
551+ -> WrapArg er EventName x
552+ -> EventTrigger t x
553+ -> IO (IO ())
554+triggerBody ctx cfg events eventTriggerRefs e (WrapArg en) t = case DMap.lookup en eventTriggerRefs of
555+ Just (EventFilterTriggerRef r) -> do
556+ writeIORef r $ Just t
557+ return $ do
558+ writeIORef r Nothing
559+ Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
560+ evt <- DOM.event
561+ mv <- lift $ unGhcjsEventHandler handler (en, GhcjsDomEvent evt)
562+ case mv of
563+ Nothing -> return ()
564+ Just v -> liftIO $ do
565+ --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
566+ ref <- newIORef $ Just t
567+ writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
568+ where
569+ -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't
570+ -- retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
571+ handler :: GhcjsEventHandler er
572+ !handler = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg
573+
574+{-# SPECIALIZE triggerBody
575+ :: DOM.JSContextRef
576+ -> RawElementConfig er Spider HydrationDomSpace
577+ -> Chan [DSum (EventTriggerRef Spider) TriggerInvocation]
578+ -> DMap EventName (EventFilterTriggerRef Spider er)
579+ -> DOM.Element
580+ -> WrapArg er EventName x
581+ -> EventTrigger Spider x
582+ -> IO (IO ())
583+ #-}
584+
585+{-# SPECIALIZE triggerBody
586+ :: DOM.JSContextRef
587+ -> RawElementConfig er Spider GhcjsDomSpace
588+ -> Chan [DSum (EventTriggerRef Spider) TriggerInvocation]
589+ -> DMap EventName (EventFilterTriggerRef Spider er)
590+ -> DOM.Element
591+ -> WrapArg er EventName x
592+ -> EventTrigger Spider x
593+ -> IO (IO ())
594+ #-}
374595
375596newtype GhcjsDomHandler a b = GhcjsDomHandler { unGhcjsDomHandler :: a -> JSM b }
376597
@@ -384,8 +605,8 @@ instance DomSpace GhcjsDomSpace where
384605 type EventSpec GhcjsDomSpace = GhcjsEventSpec
385606 type RawDocument GhcjsDomSpace = DOM.Document
386607 type RawTextNode GhcjsDomSpace = DOM.Text
608+ type RawCommentNode GhcjsDomSpace = DOM.Comment
387609 type RawElement GhcjsDomSpace = DOM.Element
388− type RawFile GhcjsDomSpace = DOM.File
389610 type RawInputElement GhcjsDomSpace = DOM.HTMLInputElement
390611 type RawTextAreaElement GhcjsDomSpace = DOM.HTMLTextAreaElement
391612 type RawSelectElement GhcjsDomSpace = DOM.HTMLSelectElement
@@ -438,137 +659,746 @@ instance er ~ EventResult => Default (GhcjsEventSpec er) where
438659 runReaderT (defaultDomEventHandler e en) evt
439660 }
440661
441−instance SupportsImmediateDomBuilder t m => NotReady t (ImmediateDomBuilderT t m) where
662+{-# INLINE makeElement #-}
663+makeElement :: MonadJSM m => Document -> Text -> ElementConfig er t s -> m DOM.Element
664+makeElement doc elementTag cfg = do
665+ e <- uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
666+ Nothing -> createElement doc elementTag
667+ Just ens -> createElementNS doc (Just ens) elementTag
668+ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
669+ Nothing -> setAttribute e n v
670+ Just ans -> setAttributeNS e (Just ans) n v
671+ pure e
672+
673+{-# INLINE elementImmediate #-}
674+elementImmediate
675+ :: ( RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, EventSpec s ~ GhcjsEventSpec
676+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m )
677+ => Text
678+ -> ElementConfig er t s
679+ -> HydrationDomBuilderT s t m a
680+ -> HydrationDomBuilderT s t m (Element er GhcjsDomSpace t, a)
681+elementImmediate elementTag cfg child = do
682+ doc <- askDocument
683+ ctx <- askJSM
684+ events <- askEvents
685+ parent <- getParent
686+ e <- makeElement doc elementTag cfg
687+ appendChild_ parent e
688+ -- Run the child builder with updated parent and previous sibling references
689+ result <- localEnv (\env -> env { _hydrationDomBuilderEnv_parent = Left $ toNode e }) child
690+ let rawCfg = extractRawElementConfig cfg
691+ eventTriggerRefs <- wrap events e rawCfg
692+ es <- newFanEventWithTrigger $ triggerBody ctx rawCfg events eventTriggerRefs e
693+ return (Element es e, result)
694+
695+{-# SPECIALIZE elementImmediate
696+ :: Text
697+ -> ElementConfig er Spider HydrationDomSpace
698+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM a
699+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM (Element er GhcjsDomSpace Spider, a)
700+ #-}
701+
702+{-# SPECIALIZE elementImmediate
703+ :: Text
704+ -> ElementConfig er Spider GhcjsDomSpace
705+ -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM a
706+ -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM (Element er GhcjsDomSpace Spider, a)
707+ #-}
708+
709+-- For specialisation
710+
711+-- | The Reflex timeline for interacting with the DOM
712+type DomTimeline =
713+#ifdef PROFILE_REFLEX
714+ ProfiledTimeline
715+#endif
716+ Spider
717+
718+-- | The ReflexHost the DOM lives in
719+type DomHost =
720+#ifdef PROFILE_REFLEX
721+ ProfiledM
722+#endif
723+ (SpiderHost Global)
724+
725+type DomCoreWidget x = PostBuildT DomTimeline (WithJSContextSingleton x (PerformEventT DomTimeline DomHost))
726+type HydrationM = DomCoreWidget ()
727+
728+{-# INLINE elementInternal #-}
729+elementInternal
730+ :: (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
731+ => Text
732+ -> ElementConfig er t HydrationDomSpace
733+ -> HydrationDomBuilderT HydrationDomSpace t m a
734+ -> HydrationDomBuilderT HydrationDomSpace t m (Element er HydrationDomSpace t, a)
735+elementInternal elementTag cfg child = getHydrationMode >>= \case
736+ HydrationMode_Immediate -> do
737+ (Element es _, result) <- elementImmediate elementTag cfg child
738+ return (Element es (), result)
739+ HydrationMode_Hydrating -> fst <$> hydrateElement elementTag cfg child
740+
741+{-# SPECIALIZE elementInternal
742+ :: Text
743+ -> ElementConfig er Spider HydrationDomSpace
744+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM a
745+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM (Element er HydrationDomSpace Spider, a)
746+ #-}
747+
748+{-# INLINE hydrateElement #-}
749+hydrateElement
750+ :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
751+ => Text
752+ -> ElementConfig er t HydrationDomSpace
753+ -> HydrationDomBuilderT HydrationDomSpace t m a
754+ -> HydrationDomBuilderT HydrationDomSpace t m ((Element er HydrationDomSpace t, a), IORef DOM.Element)
755+hydrateElement elementTag cfg child = do
756+ ctx <- askJSM
757+ events <- askEvents
758+ -- Schedule everything for after postBuild, except for getting the result itself
759+ parentRef <- liftIO $ newIORef $ error "Parent not yet initialized"
760+ e' <- liftIO $ newIORef $ error "hydrateElement: Element not yet initialized"
761+ env <- HydrationDomBuilderT ask
762+ childDelayedRef <- liftIO $ newIORef $ pure ()
763+ let env' = env
764+ { _hydrationDomBuilderEnv_parent = Right parentRef
765+ , _hydrationDomBuilderEnv_delayed = childDelayedRef
766+ }
767+ result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
768+ wrapResult <- liftIO newEmptyMVar
769+ let ssrAttr = "data-ssr" :: DOM.JSString
770+ hasSSRAttribute :: DOM.Element -> HydrationRunnerT t m Bool
771+ hasSSRAttribute e = case cfg ^. namespace of
772+ Nothing -> hasAttribute e ssrAttr <* removeAttribute e ssrAttr
773+ Just ns -> hasAttributeNS e (Just ns) ssrAttr <* removeAttributeNS e (Just ns) ssrAttr
774+ childDom <- liftIO $ readIORef childDelayedRef
775+ let rawCfg = extractRawElementConfig cfg
776+ doc <- askDocument
777+ addHydrationStep $ do
778+ parent <- askParent
779+ lastHydrationNode <- getPreviousNode
780+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
781+ Nothing -> do -- ran out of nodes, create the element
782+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
783+ e <- makeElement doc elementTag cfg
784+ insertAfterPreviousNode e
785+ pure e
786+ Just node -> DOM.castTo DOM.Element node >>= \case
787+ Nothing -> go (Just node) -- this node is not an element, skip
788+ Just e -> hasSSRAttribute e >>= \case
789+ False -> go (Just node) -- this element was not added by the static renderer, skip
790+ True -> do
791+ t <- Element.getTagName e
792+ -- TODO: check attributes?
793+ if T.toCaseFold elementTag == T.toCaseFold t
794+ then pure e
795+ -- we came to some other statically rendered element, so something has gone wrong
796+ else do
797+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
798+ n <- makeElement doc elementTag cfg
799+ insertAfterPreviousNode n
800+ pure n
801+ e <- go lastHydrationNode
802+ setPreviousNode $ Just $ toNode e
803+ -- Update the parent node used by the children
804+ liftIO $ writeIORef parentRef $ toNode e
805+ liftIO $ writeIORef e' e
806+ -- Setup events, store the result so we can wait on it later
807+ refs <- wrap events e rawCfg
808+ liftIO $ putMVar wrapResult (e, refs)
809+ localRunner childDom Nothing $ toNode e
810+ -- We need the EventSelector to switch to the real event handler after activation
811+ es <- newFanEventWithTrigger $ \(WrapArg en) t -> do
812+ cleanup <- newEmptyMVar
813+ threadId <- forkIO $ do
814+ -- Wait on the data we need from the delayed action
815+ (e, eventTriggerRefs) <- readMVar wrapResult
816+ bracketOnError
817+ -- Run the setup, acquiring the cleanup action
818+ (triggerBody ctx rawCfg events eventTriggerRefs e (WrapArg en) t)
819+ -- Run the cleanup, if we have it - but only when an exception is
820+ -- raised (we might get killed between acquiring the cleanup action
821+ -- from 'triggerBody' and putting it in the MVar)
822+ id
823+ -- Try to put this action into the cleanup MVar
824+ (putMVar cleanup)
825+ pure $ do
826+ tryReadMVar cleanup >>= \case
827+ Nothing -> killThread threadId
828+ Just c -> c
829+ return ((Element es (), result), e')
830+
831+{-# SPECIALIZE hydrateElement
832+ :: Text
833+ -> ElementConfig er Spider HydrationDomSpace
834+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM a
835+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM ((Element er HydrationDomSpace Spider, a), IORef DOM.Element)
836+ #-}
837+
838+{-# INLINE inputElementImmediate #-}
839+inputElementImmediate
840+ :: ( RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, EventSpec s ~ GhcjsEventSpec
841+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
842+ , MonadRef m, Ref m ~ IORef )
843+ => InputElementConfig er t s -> HydrationDomBuilderT s t m (InputElement er GhcjsDomSpace t)
844+inputElementImmediate cfg = do
845+ (e@(Element eventSelector domElement), ()) <- elementImmediate "input" (_inputElementConfig_elementConfig cfg) $ return ()
846+ let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
847+ Input.setValue domInputElement $ cfg ^. inputElementConfig_initialValue
848+ v0 <- Input.getValue domInputElement
849+ let getMyValue = Input.getValue domInputElement
850+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select eventSelector (WrapArg Input)
851+ valueChangedBySetValue <- case _inputElementConfig_setValue cfg of
852+ Nothing -> return never
853+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
854+ Input.setValue domInputElement v'
855+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
856+ v <- holdDyn v0 $ leftmost
857+ [ valueChangedBySetValue
858+ , valueChangedByUI
859+ ]
860+ Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
861+ checkedChangedByUI <- wrapDomEvent domInputElement (`on` Events.click) $ do
862+ Input.getChecked domInputElement
863+ checkedChangedBySetChecked <- case _inputElementConfig_setChecked cfg of
864+ Nothing -> return never
865+ Just eNewchecked -> requestDomAction $ ffor eNewchecked $ \newChecked -> do
866+ oldChecked <- Input.getChecked domInputElement
867+ Input.setChecked domInputElement newChecked
868+ return $ if newChecked /= oldChecked
869+ then Just newChecked
870+ else Nothing
871+ c <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
872+ [ fmapMaybe id checkedChangedBySetChecked
873+ , checkedChangedByUI
874+ ]
875+ hasFocus <- mkHasFocus e
876+ files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
877+ mfiles <- Input.getFiles domInputElement
878+ let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
879+ maybe (return []) getMyFiles mfiles
880+ checked <- holdUniqDyn c
881+ return $ InputElement
882+ { _inputElement_value = v
883+ , _inputElement_checked = checked
884+ , _inputElement_checkedChange = checkedChangedByUI
885+ , _inputElement_input = valueChangedByUI
886+ , _inputElement_hasFocus = hasFocus
887+ , _inputElement_element = e
888+ , _inputElement_raw = domInputElement
889+ , _inputElement_files = files
890+ }
891+
892+{-# INLINE inputElementInternal #-}
893+inputElementInternal
894+ :: ( MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
895+ , MonadRef m, Ref m ~ IORef )
896+ => InputElementConfig er t HydrationDomSpace -> HydrationDomBuilderT HydrationDomSpace t m (InputElement er HydrationDomSpace t)
897+inputElementInternal cfg = getHydrationMode >>= \case
898+ HydrationMode_Immediate -> ffor (inputElementImmediate cfg) $ \result -> result
899+ { _inputElement_element = Element (_element_events $ _inputElement_element result) ()
900+ , _inputElement_raw = ()
901+ }
902+ HydrationMode_Hydrating -> do
903+ ((e, _), domElementRef) <- hydrateElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
904+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
905+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
906+ (focusChange, triggerFocusChange) <- newTriggerEvent
907+ (checkedChangedByUI, triggerCheckedChangedByUI) <- newTriggerEvent
908+ (checkedChangedBySetChecked, triggerCheckedChangedBySetChecked) <- newTriggerEvent
909+ (fileChange, triggerFileChange) <- newTriggerEvent
910+ doc <- askDocument
911+ -- Expected initial value from config
912+ let v0 = _inputElementConfig_initialValue cfg
913+ addHydrationStep $ do
914+ domElement <- liftIO $ readIORef domElementRef
915+ let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
916+ getValue = Input.getValue domInputElement
917+ -- The browser might have messed with the value, or the user could have
918+ -- altered it before activation, so we set it if it isn't what we expect
919+ liftJSM getValue >>= \v0' -> do
920+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
921+ -- Watch for user interaction and trigger event accordingly
922+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Input)
923+ for_ (_inputElementConfig_setValue cfg) $ \eSetValue ->
924+ requestDomAction_ $ ffor eSetValue $ \v' -> do
925+ Input.setValue domInputElement v'
926+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
927+ liftIO $ triggerChangeBySetValue v
928+ let focusChange' = leftmost
929+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
930+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
931+ ]
932+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
933+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
934+ Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
935+ _ <- liftJSM $ domInputElement `on` Events.click $ do
936+ liftIO . triggerCheckedChangedByUI =<< Input.getChecked domInputElement
937+ for_ (_inputElementConfig_setChecked cfg) $ \eNewchecked ->
938+ requestDomAction $ ffor eNewchecked $ \newChecked -> do
939+ oldChecked <- Input.getChecked domInputElement
940+ Input.setChecked domInputElement newChecked
941+ when (newChecked /= oldChecked) $ liftIO $ triggerCheckedChangedBySetChecked newChecked
942+ _ <- liftJSM $ domInputElement `on` Events.change $ do
943+ mfiles <- Input.getFiles domInputElement
944+ let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
945+ liftIO . triggerFileChange =<< maybe (return []) getMyFiles mfiles
946+ return ()
947+ checked' <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
948+ [ checkedChangedBySetChecked
949+ , checkedChangedByUI
950+ ]
951+ checked <- holdUniqDyn checked'
952+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
953+ hasFocus <- holdUniqDyn =<< holdDyn initialFocus focusChange
954+ v <- holdDyn v0 $ leftmost
955+ [ valueChangedBySetValue
956+ , valueChangedByUI
957+ ]
958+ files <- holdDyn mempty fileChange
959+ return $ InputElement
960+ { _inputElement_value = v
961+ , _inputElement_checked = checked
962+ , _inputElement_checkedChange = checkedChangedByUI
963+ , _inputElement_input = valueChangedByUI
964+ , _inputElement_hasFocus = hasFocus
965+ , _inputElement_element = e
966+ , _inputElement_raw = ()
967+ , _inputElement_files = files
968+ }
969+
970+{-# INLINE textAreaElementImmediate #-}
971+textAreaElementImmediate
972+ :: ( RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, EventSpec s ~ GhcjsEventSpec
973+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
974+ , MonadRef m, Ref m ~ IORef )
975+ => TextAreaElementConfig er t s -> HydrationDomBuilderT s t m (TextAreaElement er GhcjsDomSpace t)
976+textAreaElementImmediate cfg = do
977+ (e@(Element eventSelector domElement), _) <- elementImmediate "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
978+ let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
979+ TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
980+ v0 <- TextArea.getValue domTextAreaElement
981+ let getMyValue = TextArea.getValue domTextAreaElement
982+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select eventSelector (WrapArg Input)
983+ valueChangedBySetValue <- case _textAreaElementConfig_setValue cfg of
984+ Nothing -> return never
985+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
986+ TextArea.setValue domTextAreaElement v'
987+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
988+ v <- holdDyn v0 $ leftmost
989+ [ valueChangedBySetValue
990+ , valueChangedByUI
991+ ]
992+ hasFocus <- mkHasFocus e
993+ return $ TextAreaElement
994+ { _textAreaElement_value = v
995+ , _textAreaElement_input = valueChangedByUI
996+ , _textAreaElement_hasFocus = hasFocus
997+ , _textAreaElement_element = e
998+ , _textAreaElement_raw = domTextAreaElement
999+ }
1000+
1001+{-# INLINE textAreaElementInternal #-}
1002+textAreaElementInternal
1003+ :: ( MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
1004+ , MonadRef m, Ref m ~ IORef )
1005+ => TextAreaElementConfig er t HydrationDomSpace -> HydrationDomBuilderT HydrationDomSpace t m (TextAreaElement er HydrationDomSpace t)
1006+textAreaElementInternal cfg = getHydrationMode >>= \case
1007+ HydrationMode_Immediate -> ffor (textAreaElementImmediate cfg) $ \result -> result
1008+ { _textAreaElement_element = Element (_element_events $ _textAreaElement_element result) ()
1009+ , _textAreaElement_raw = ()
1010+ }
1011+ HydrationMode_Hydrating -> do
1012+ ((e, _), domElementRef) <- hydrateElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
1013+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
1014+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
1015+ (focusChange, triggerFocusChange) <- newTriggerEvent
1016+ doc <- askDocument
1017+ -- Expected initial value from config
1018+ let v0 = _textAreaElementConfig_initialValue cfg
1019+ addHydrationStep $ do
1020+ domElement <- liftIO $ readIORef domElementRef
1021+ let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
1022+ getValue = TextArea.getValue domTextAreaElement
1023+ -- The browser might have messed with the value, or the user could have
1024+ -- altered it before activation, so we set it if it isn't what we expect
1025+ liftJSM getValue >>= \v0' -> do
1026+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
1027+ -- Watch for user interaction and trigger event accordingly
1028+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Input)
1029+ for_ (_textAreaElementConfig_setValue cfg) $ \eSetValue ->
1030+ requestDomAction_ $ ffor eSetValue $ \v' -> do
1031+ TextArea.setValue domTextAreaElement v'
1032+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
1033+ liftIO $ triggerChangeBySetValue v
1034+ let focusChange' = leftmost
1035+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
1036+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1037+ ]
1038+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
1039+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
1040+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
1041+ hasFocus <- holdUniqDyn =<< holdDyn initialFocus focusChange
1042+ v <- holdDyn v0 $ leftmost
1043+ [ valueChangedBySetValue
1044+ , valueChangedByUI
1045+ ]
1046+ return $ TextAreaElement
1047+ { _textAreaElement_value = v
1048+ , _textAreaElement_input = valueChangedByUI
1049+ , _textAreaElement_hasFocus = hasFocus
1050+ , _textAreaElement_element = e
1051+ , _textAreaElement_raw = ()
1052+ }
1053+
1054+{-# INLINE selectElementImmediate #-}
1055+selectElementImmediate
1056+ :: ( EventSpec s ~ GhcjsEventSpec, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document
1057+ , MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m )
1058+ => SelectElementConfig er t s
1059+ -> HydrationDomBuilderT s t m a
1060+ -> HydrationDomBuilderT s t m (SelectElement er GhcjsDomSpace t, a)
1061+selectElementImmediate cfg child = do
1062+ (e@(Element eventSelector domElement), result) <- elementImmediate "select" (cfg ^. selectElementConfig_elementConfig) child
1063+ let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
1064+ Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
1065+ v0 <- Select.getValue domSelectElement
1066+ let getMyValue = Select.getValue domSelectElement
1067+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select eventSelector (WrapArg Change)
1068+ valueChangedBySetValue <- case _selectElementConfig_setValue cfg of
1069+ Nothing -> return never
1070+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
1071+ Select.setValue domSelectElement v'
1072+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
1073+ v <- holdDyn v0 $ leftmost
1074+ [ valueChangedBySetValue
1075+ , valueChangedByUI
1076+ ]
1077+ hasFocus <- mkHasFocus e
1078+ let wrapped = SelectElement
1079+ { _selectElement_value = v
1080+ , _selectElement_change = valueChangedByUI
1081+ , _selectElement_hasFocus = hasFocus
1082+ , _selectElement_element = e
1083+ , _selectElement_raw = domSelectElement
1084+ }
1085+ return (wrapped, result)
1086+
1087+{-# INLINE selectElementInternal #-}
1088+selectElementInternal
1089+ :: ( MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m, MonadHold t m
1090+ , MonadRef m, Ref m ~ IORef )
1091+ => SelectElementConfig er t HydrationDomSpace
1092+ -> HydrationDomBuilderT HydrationDomSpace t m a
1093+ -> HydrationDomBuilderT HydrationDomSpace t m (SelectElement er HydrationDomSpace t, a)
1094+selectElementInternal cfg child = getHydrationMode >>= \case
1095+ HydrationMode_Immediate -> ffor (selectElementImmediate cfg child) $ \(e, result) -> (e
1096+ { _selectElement_element = Element (_element_events $ _selectElement_element e) ()
1097+ , _selectElement_raw = ()
1098+ }, result)
1099+ HydrationMode_Hydrating -> do
1100+ ((e, result), domElementRef) <- hydrateElement "select" (cfg ^. selectElementConfig_elementConfig) child
1101+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
1102+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
1103+ (focusChange, triggerFocusChange) <- newTriggerEvent
1104+ doc <- askDocument
1105+ -- Expected initial value from config
1106+ let v0 = _selectElementConfig_initialValue cfg
1107+ addHydrationStep $ do
1108+ domElement <- liftIO $ readIORef domElementRef
1109+ let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
1110+ getValue = Select.getValue domSelectElement
1111+ -- The browser might have messed with the value, or the user could have
1112+ -- altered it before activation, so we set it if it isn't what we expect
1113+ liftJSM getValue >>= \v0' -> do
1114+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
1115+ -- Watch for user interaction and trigger event accordingly
1116+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Change)
1117+ for_ (_selectElementConfig_setValue cfg) $ \eSetValue ->
1118+ requestDomAction_ $ ffor eSetValue $ \v' -> do
1119+ Select.setValue domSelectElement v'
1120+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
1121+ liftIO $ triggerChangeBySetValue v
1122+ let focusChange' = leftmost
1123+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
1124+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1125+ ]
1126+ liftIO . triggerFocusChange =<< Node.isSameNode (toNode domElement) . fmap toNode =<< Document.getActiveElement doc
1127+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
1128+ let initialFocus = False -- Assume it isn't focused, but we update the actual focus state at switchover
1129+ hasFocus <- holdUniqDyn =<< holdDyn initialFocus focusChange
1130+ v <- holdDyn v0 $ leftmost
1131+ [ valueChangedBySetValue
1132+ , valueChangedByUI
1133+ ]
1134+ return $ (,result) $ SelectElement
1135+ { _selectElement_value = v
1136+ , _selectElement_change = valueChangedByUI
1137+ , _selectElement_hasFocus = hasFocus
1138+ , _selectElement_element = e
1139+ , _selectElement_raw = ()
1140+ }
1141+
1142+{-# INLINE textNodeImmediate #-}
1143+textNodeImmediate
1144+ :: (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, MonadJSM m, Reflex t, MonadFix m)
1145+ => TextNodeConfig t -> HydrationDomBuilderT s t m DOM.Text
1146+textNodeImmediate (TextNodeConfig !t mSetContents) = do
1147+ p <- getParent
1148+ doc <- askDocument
1149+ n <- createTextNode doc t
1150+ appendChild_ p n
1151+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1152+ pure n
1153+
1154+{-# SPECIALIZE textNodeImmediate
1155+ :: TextNodeConfig DomTimeline
1156+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM DOM.Text
1157+ #-}
1158+
1159+{-# SPECIALIZE textNodeImmediate
1160+ :: TextNodeConfig DomTimeline
1161+ -> HydrationDomBuilderT GhcjsDomSpace DomTimeline HydrationM DOM.Text
1162+ #-}
1163+
1164+{-# INLINE textNodeInternal #-}
1165+textNodeInternal
1166+ :: (Adjustable t m, MonadHold t m, MonadJSM m, MonadFix m, Reflex t)
1167+ => TextNodeConfig t -> HydrationDomBuilderT HydrationDomSpace t m (TextNode HydrationDomSpace t)
1168+textNodeInternal tc@(TextNodeConfig !t mSetContents) = do
1169+ doc <- askDocument
1170+ getHydrationMode >>= \case
1171+ HydrationMode_Immediate -> void $ textNodeImmediate tc
1172+ HydrationMode_Hydrating -> addHydrationStepWithSetup (maybe (pure $ pure t) (hold t) mSetContents) $ \currentText -> do
1173+ n <- hydrateTextNode doc =<< sample currentText
1174+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1175+ pure $ TextNode ()
1176+
1177+{-# SPECIALIZE textNodeInternal
1178+ :: TextNodeConfig DomTimeline
1179+ -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (TextNode HydrationDomSpace DomTimeline)
1180+ #-}
1181+
1182+-- | The static builder mashes adjacent text nodes into one node: we check the
1183+-- text content of each node we come to, comparing it to the content we
1184+-- expect. We also have a special case for empty text nodes - we always create
1185+-- the and add them after the previous node reference.
1186+{-# INLINE hydrateTextNode #-}
1187+hydrateTextNode :: MonadJSM m => Document -> Text -> HydrationRunnerT t m DOM.Text
1188+hydrateTextNode doc t@"" = do
1189+ tn <- createTextNode doc t
1190+ insertAfterPreviousNode tn
1191+ pure tn
1192+hydrateTextNode doc t = do
1193+ n <- join $ go <$> askParent <*> getPreviousNode
1194+ setPreviousNode $ Just $ toNode n
1195+ return n
1196+ where
1197+ go parent mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1198+ Nothing -> do
1199+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1200+ n <- createTextNode doc t
1201+ insertAfterPreviousNode n
1202+ pure n
1203+ Just node -> DOM.castTo DOM.Text node >>= \case
1204+ Nothing -> go parent $ Just node
1205+ Just originalNode -> do
1206+ originalText <- Node.getTextContentUnchecked originalNode
1207+ case T.stripPrefix t originalText of
1208+ Just "" -> return originalNode
1209+ Just _ -> do
1210+ -- If we have the right prefix, we split the text node into a node containing the
1211+ -- required text and a subsequent sibling node containing the rest of the text.
1212+ DOM.splitText_ originalNode $ fromIntegral $ T.length t
1213+ return originalNode
1214+ Nothing -> do
1215+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1216+ n <- createTextNode doc t
1217+ insertAfterPreviousNode n
1218+ pure n
1219+
1220+{-# INLINE commentNodeImmediate #-}
1221+commentNodeImmediate
1222+ :: (RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document, MonadJSM m, Reflex t, MonadFix m)
1223+ => CommentNodeConfig t -> HydrationDomBuilderT s t m DOM.Comment
1224+commentNodeImmediate (CommentNodeConfig !t mSetContents) = do
1225+ p <- getParent
1226+ doc <- askDocument
1227+ n <- createComment doc t
1228+ appendChild_ p n
1229+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1230+ pure n
1231+
1232+{-# INLINE commentNodeInternal #-}
1233+commentNodeInternal
1234+ :: (Ref m ~ IORef, MonadRef m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadJSM (Performable m), MonadJSM m, MonadFix m, Reflex t, Adjustable t m, MonadHold t m, MonadSample t m)
1235+ => CommentNodeConfig t -> HydrationDomBuilderT HydrationDomSpace t m (CommentNode HydrationDomSpace t)
1236+commentNodeInternal tc@(CommentNodeConfig t0 mSetContents) = do
1237+ doc <- askDocument
1238+ getHydrationMode >>= \case
1239+ HydrationMode_Immediate -> void $ commentNodeInternal tc
1240+ HydrationMode_Hydrating -> addHydrationStepWithSetup (maybe (pure $ pure t0) (hold t0) mSetContents) $ \bt -> do
1241+ t <- sample bt
1242+ void $ hydrateComment doc t mSetContents
1243+ pure $ CommentNode ()
1244+
1245+{-# INLINE hydrateComment #-}
1246+hydrateComment :: (MonadJSM m, Reflex t, MonadFix m) => Document -> Text -> Maybe (Event t Text) -> HydrationRunnerT t m DOM.Comment
1247+hydrateComment doc t mSetContents = do
1248+ parent <- askParent
1249+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1250+ Nothing -> do
1251+ c <- createComment doc t
1252+ insertAfterPreviousNode c
1253+ pure c
1254+ Just node -> DOM.castTo DOM.Comment node >>= \case
1255+ Nothing -> go (Just node)
1256+ Just c -> do
1257+ t' <- Node.getTextContentUnchecked c
1258+ if t == t'
1259+ then pure c
1260+ else do
1261+ c' <- createComment doc t
1262+ insertAfterPreviousNode c'
1263+ pure c'
1264+ n <- go =<< getPreviousNode
1265+ setPreviousNode $ Just $ toNode n
1266+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
1267+ pure n
1268+
1269+-- | We leave markers in the static builder as comments, and rip these comments
1270+-- out at hydration time, replacing them with empty text nodes.
1271+{-# INLINABLE skipToAndReplaceComment #-}
1272+skipToAndReplaceComment
1273+ :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1274+ => Text
1275+ -> IORef Text
1276+ -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1277+skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1278+ HydrationMode_Immediate -> do
1279+ -- If we're in immediate mode, we don't try to replace an existing comment,
1280+ -- and just return a dummy key
1281+ t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
1282+ append $ toNode t
1283+ textNodeRef <- liftIO $ newIORef t
1284+ keyRef <- liftIO $ newIORef ""
1285+ pure (pure (), textNodeRef, keyRef)
1286+ HydrationMode_Hydrating -> do
1287+ doc <- askDocument
1288+ textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
1289+ keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1290+ let go key0 mLastNode = do
1291+ parent <- askParent
1292+ node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
1293+ DOM.castTo DOM.Comment node >>= \case
1294+ Just comment -> do
1295+ commentText <- Node.getTextContentUnchecked comment
1296+ case T.stripPrefix (prefix <> key0) commentText of
1297+ Just key -> do
1298+ -- Replace the comment with an (invisible) text node
1299+ tn <- createTextNode doc ("" :: Text)
1300+ Node.replaceChild_ parent tn comment
1301+ pure (tn, key)
1302+ Nothing -> do
1303+ go key0 (Just node)
1304+ Nothing -> do
1305+ go key0 (Just node)
1306+ switchComment = do
1307+ key0 <- liftIO $ readIORef key0Ref
1308+ (tn, key) <- go key0 =<< getPreviousNode
1309+ setPreviousNode $ Just $ toNode tn
1310+ liftIO $ do
1311+ writeIORef textNodeRef tn
1312+ writeIORef keyRef key
1313+ pure (switchComment, textNodeRef, keyRef)
1314+
1315+{-# INLINABLE skipToReplaceStart #-}
1316+skipToReplaceStart :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1317+skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef "")
1318+
1319+{-# INLINABLE skipToReplaceEnd #-}
1320+skipToReplaceEnd :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => IORef Text -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text)
1321+skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
1322+
1323+instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
4421324 notReadyUntil e = do
4431325 eOnce <- headE e
444− env <- ImmediateDomBuilderT ask
445− let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
1326+ unreadyChildren <- askUnreadyChildren
1327+ commitAction <- askCommitAction
4461328 liftIO $ modifyIORef' unreadyChildren succ
4471329 let ready = do
4481330 old <- liftIO $ readIORef unreadyChildren
4491331 let new = pred old
4501332 liftIO $ writeIORef unreadyChildren $! new
451− when (new == 0) $ _immediateDomBuilderEnv_commitAction env
1333+ when (new == 0) commitAction
4521334 requestDomAction_ $ ready <$ eOnce
4531335 notReady = do
454− env <- ImmediateDomBuilderT ask
455− let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env
1336+ unreadyChildren <- askUnreadyChildren
4561337 liftIO $ modifyIORef' unreadyChildren succ
4571338
458−instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t m) where
459− type DomBuilderSpace (ImmediateDomBuilderT t m) = GhcjsDomSpace
1339+data HydrationDomSpace
1340+
1341+instance DomSpace HydrationDomSpace where
1342+ type EventSpec HydrationDomSpace = GhcjsEventSpec
1343+ type RawDocument HydrationDomSpace = DOM.Document
1344+ type RawTextNode HydrationDomSpace = ()
1345+ type RawCommentNode HydrationDomSpace = ()
1346+ type RawElement HydrationDomSpace = ()
1347+ type RawInputElement HydrationDomSpace = ()
1348+ type RawTextAreaElement HydrationDomSpace = ()
1349+ type RawSelectElement HydrationDomSpace = ()
1350+ addEventSpecFlags _ en f es = es
1351+ { _ghcjsEventSpec_filters =
1352+ let f' = Just . GhcjsEventFilter . \case
1353+ Nothing -> \evt -> do
1354+ mEventResult <- unGhcjsEventHandler (_ghcjsEventSpec_handler es) (en, evt)
1355+ return (f mEventResult, return mEventResult)
1356+ Just (GhcjsEventFilter oldFilter) -> \evt -> do
1357+ (oldFlags, oldContinuation) <- oldFilter evt
1358+ mEventResult <- oldContinuation
1359+ let newFlags = oldFlags <> f mEventResult
1360+ return (newFlags, return mEventResult)
1361+ in DMap.alter f' en $ _ghcjsEventSpec_filters es
1362+ }
1363+
1364+instance SupportsHydrationDomBuilder t m => DomBuilder t (HydrationDomBuilderT HydrationDomSpace t m) where
1365+ type DomBuilderSpace (HydrationDomBuilderT HydrationDomSpace t m) = HydrationDomSpace
1366+ {-# INLINABLE element #-}
1367+ element = elementInternal
4601368 {-# INLINABLE textNode #-}
461− textNode (TextNodeConfig initialContents mSetContents) = do
462− n <- textNodeInternal initialContents
463− mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
464− return $ TextNode n
1369+ textNode = textNodeInternal
1370+ {-# INLINABLE commentNode #-}
1371+ commentNode = commentNodeInternal
1372+ {-# INLINABLE inputElement #-}
1373+ inputElement = inputElementInternal
1374+ {-# INLINABLE textAreaElement #-}
1375+ textAreaElement = textAreaElementInternal
1376+ {-# INLINABLE selectElement #-}
1377+ selectElement = selectElementInternal
1378+ placeRawElement () = pure ()
1379+ wrapRawElement () _cfg = pure $ Element (EventSelector $ const never) ()
1380+
1381+instance SupportsHydrationDomBuilder t m => DomBuilder t (HydrationDomBuilderT GhcjsDomSpace t m) where
1382+ type DomBuilderSpace (HydrationDomBuilderT GhcjsDomSpace t m) = GhcjsDomSpace
4651383 {-# INLINABLE element #-}
466− element elementTag cfg child = fst <$> makeElement elementTag cfg child
1384+ element = elementImmediate
1385+ {-# INLINABLE textNode #-}
1386+ textNode = fmap TextNode . textNodeImmediate
1387+ {-# INLINABLE commentNode #-}
1388+ commentNode = fmap CommentNode . commentNodeImmediate
4671389 {-# INLINABLE inputElement #-}
468− inputElement cfg = do
469− ((e, _), domElement) <- makeElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
470− let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
471− Input.setValue domInputElement $ cfg ^. inputElementConfig_initialValue
472− v0 <- Input.getValue domInputElement
473− let getMyValue = Input.getValue domInputElement
474− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
475− valueChangedBySetValue <- case _inputElementConfig_setValue cfg of
476− Nothing -> return never
477− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
478− Input.setValue domInputElement v'
479− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
480− v <- holdDyn v0 $ leftmost
481− [ valueChangedBySetValue
482− , valueChangedByUI
483− ]
484− Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
485− checkedChangedByUI <- wrapDomEvent domInputElement (`on` Events.click) $ do
486− Input.getChecked domInputElement
487− checkedChangedBySetChecked <- case _inputElementConfig_setChecked cfg of
488− Nothing -> return never
489− Just eNewchecked -> requestDomAction $ ffor eNewchecked $ \newChecked -> do
490− oldChecked <- Input.getChecked domInputElement
491− Input.setChecked domInputElement newChecked
492− return $ if newChecked /= oldChecked
493− then Just newChecked
494− else Nothing
495− c <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
496− [ fmapMaybe id checkedChangedBySetChecked
497− , checkedChangedByUI
498− ]
499− let initialFocus = False --TODO: Is this correct?
500− hasFocus <- holdDyn initialFocus $ leftmost
501− [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
502− , True <$ Reflex.select (_element_events e) (WrapArg Focus)
503− ]
504− files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
505− mfiles <- Input.getFiles domInputElement
506− let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
507− maybe (return []) getMyFiles mfiles
508− checked <- holdUniqDyn c
509− return $ InputElement
510− { _inputElement_value = v
511− , _inputElement_checked = checked
512− , _inputElement_checkedChange = checkedChangedByUI
513− , _inputElement_input = valueChangedByUI
514− , _inputElement_hasFocus = hasFocus
515− , _inputElement_element = e
516− , _inputElement_raw = domInputElement
517− , _inputElement_files = files
518− }
1390+ inputElement = inputElementImmediate
5191391 {-# INLINABLE textAreaElement #-}
520− textAreaElement cfg = do --TODO
521− ((e, _), domElement) <- makeElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
522− let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
523− TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
524− v0 <- TextArea.getValue domTextAreaElement
525− let getMyValue = TextArea.getValue domTextAreaElement
526− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
527− valueChangedBySetValue <- case _textAreaElementConfig_setValue cfg of
528− Nothing -> return never
529− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
530− TextArea.setValue domTextAreaElement v'
531− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
532− v <- holdDyn v0 $ leftmost
533− [ valueChangedBySetValue
534− , valueChangedByUI
535− ]
536− hasFocus <- mkHasFocus e
537− return $ TextAreaElement
538− { _textAreaElement_value = v
539− , _textAreaElement_input = valueChangedByUI
540− , _textAreaElement_hasFocus = hasFocus
541− , _textAreaElement_element = e
542− , _textAreaElement_raw = domTextAreaElement
543− }
1392+ textAreaElement = textAreaElementImmediate
5441393 {-# INLINABLE selectElement #-}
545− selectElement cfg child = do
546− ((e, result), domElement) <- makeElement "select" (cfg ^. selectElementConfig_elementConfig) child
547− let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
548− Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
549− v0 <- Select.getValue domSelectElement
550− let getMyValue = Select.getValue domSelectElement
551− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Change)
552− valueChangedBySetValue <- case _selectElementConfig_setValue cfg of
553− Nothing -> return never
554− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
555− Select.setValue domSelectElement v'
556− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
557− v <- holdDyn v0 $ leftmost
558− [ valueChangedBySetValue
559− , valueChangedByUI
560− ]
561− hasFocus <- mkHasFocus e
562− let wrapped = SelectElement
563− { _selectElement_value = v
564− , _selectElement_change = valueChangedByUI
565− , _selectElement_hasFocus = hasFocus
566− , _selectElement_element = e
567− , _selectElement_raw = domSelectElement
568− }
569− return (wrapped, result)
1394+ selectElement = selectElementImmediate
5701395 placeRawElement = append . toNode
571− wrapRawElement = wrap
1396+ wrapRawElement e rawCfg = do
1397+ events <- askEvents
1398+ ctx <- askJSM
1399+ eventTriggerRefs <- wrap events e rawCfg
1400+ es <- newFanEventWithTrigger $ triggerBody ctx rawCfg events eventTriggerRefs e
1401+ pure $ Element es e
5721402
5731403data FragmentState
5741404 = FragmentState_Unmounted
@@ -588,21 +1418,21 @@ extractFragment fragment = do
5881418 extractBetweenExclusive (_immediateDomFragment_document fragment) before after
5891419 liftIO $ writeIORef (_immediateDomFragment_state fragment) FragmentState_Unmounted
5901420
591−instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomBuilderT t m) where
592− type DomFragment (ImmediateDomBuilderT t m) = ImmediateDomFragment
1421+instance SupportsHydrationDomBuilder t m => MountableDomBuilder t (HydrationDomBuilderT GhcjsDomSpace t m) where
1422+ type DomFragment (HydrationDomBuilderT GhcjsDomSpace t m) = ImmediateDomFragment
5931423 buildDomFragment w = do
5941424 df <- createDocumentFragment =<< askDocument
5951425 result <- flip localEnv w $ \env -> env
596− { _immediateDomBuilderEnv_parent = toNode df
1426+ { _hydrationDomBuilderEnv_parent = Left $ toNode df
5971427 }
5981428 state <- liftIO $ newIORef FragmentState_Unmounted
5991429 return (ImmediateDomFragment df state, result)
6001430 mountDomFragment fragment setFragment = do
601− parent <- askParent
1431+ parent <- getParent
6021432 extractFragment fragment
603− before <- textNodeInternal ("" :: Text)
1433+ before <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
6041434 appendChild_ parent $ _immediateDomFragment_document fragment
605− after <- textNodeInternal ("" :: Text)
1435+ after <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
6061436 xs <- foldDyn (\new (previous, _) -> (new, Just previous)) (fragment, Nothing) setFragment
6071437 requestDomAction_ $ ffor (updated xs) $ \(childFragment, Just previousFragment) -> do
6081438 extractFragment previousFragment
@@ -611,12 +1441,19 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB
6111441 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
6121442 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
6131443
614−instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
1444+instance (Reflex t, Monad m, Adjustable t m, MonadHold t m, MonadFix m) => Adjustable t (DomRenderHookT t m) where
1445+ runWithReplace a0 a' = DomRenderHookT $ runWithReplace (unDomRenderHookT a0) (fmapCheap unDomRenderHookT a')
1446+ traverseIntMapWithKeyWithAdjust f m = DomRenderHookT . traverseIntMapWithKeyWithAdjust (\k -> unDomRenderHookT . f k) m
1447+ traverseDMapWithKeyWithAdjust f m = DomRenderHookT . traverseDMapWithKeyWithAdjust (\k -> unDomRenderHookT . f k) m
1448+ traverseDMapWithKeyWithAdjustWithMove f m = DomRenderHookT . traverseDMapWithKeyWithAdjustWithMove (\k -> unDomRenderHookT . f k) m
1449+
1450+instance (Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => Adjustable t (HydrationDomBuilderT s t m) where
6151451 {-# INLINABLE runWithReplace #-}
6161452 runWithReplace a0 a' = do
617− initialEnv <- ImmediateDomBuilderT ask
618− before <- textNodeInternal ("" :: Text)
619− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
1453+ initialEnv <- HydrationDomBuilderT ask
1454+ let hydrating = _hydrationDomBuilderEnv_hydrationMode initialEnv
1455+ (hydrateStart, before, beforeKey) <- skipToReplaceStart
1456+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
6201457 haveEverBeenReady <- liftIO $ newIORef False
6211458 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
6221459 let myCommitAction = do
@@ -627,79 +1464,117 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
6271464 old <- liftIO $ readIORef parentUnreadyChildren
6281465 let new = pred old
6291466 liftIO $ writeIORef parentUnreadyChildren $! new
630− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
1467+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
6311468 -- We draw 'after' in this roundabout way to avoid using MonadFix
6321469 doc <- askDocument
633− after <- createTextNode doc ("" :: Text)
1470+ parent <- getParent
1471+ (hydrateEnd, after) <- skipToReplaceEnd beforeKey
6341472 let drawInitialChild = do
1473+ h <- liftIO $ readIORef hydrating
1474+ p' <- case h of
1475+ HydrationMode_Hydrating -> pure parent
1476+ HydrationMode_Immediate -> toNode <$> createDocumentFragment doc
6351477 unreadyChildren <- liftIO $ newIORef 0
636− let f = do
637− result <- a0
638− append $ toNode after
639− return result
640− result <- runReaderT (unImmediateDomBuilderT f) $ initialEnv
641− { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
642− , _immediateDomBuilderEnv_commitAction = myCommitAction
1478+ let a0' = case h of
1479+ HydrationMode_Hydrating -> a0
1480+ HydrationMode_Immediate -> do
1481+ a <- a0
1482+ insertBefore p' =<< liftIO (readIORef after)
1483+ pure a
1484+ delayed <- case h of
1485+ HydrationMode_Hydrating -> liftIO $ newIORef $ pure ()
1486+ HydrationMode_Immediate -> pure $ _hydrationDomBuilderEnv_delayed initialEnv
1487+ result <- runReaderT (unHydrationDomBuilderT a0') initialEnv
1488+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1489+ , _hydrationDomBuilderEnv_commitAction = myCommitAction
1490+ , _hydrationDomBuilderEnv_parent = Left p'
1491+ , _hydrationDomBuilderEnv_delayed = delayed
6431492 }
1493+ dom <- case h of
1494+ HydrationMode_Hydrating -> liftIO $ readIORef delayed
1495+ HydrationMode_Immediate -> pure $ pure ()
6441496 liftIO $ readIORef unreadyChildren >>= \case
6451497 0 -> writeIORef haveEverBeenReady True
6461498 _ -> modifyIORef' parentUnreadyChildren succ
647− return result
1499+ return (dom, result)
6481500 a'' <- numberOccurrences a'
649− (result0, child') <- ImmediateDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do
650− df <- createDocumentFragment doc
1501+ ((hydrate0, result0), child') <- HydrationDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do
1502+ h <- liftIO $ readIORef hydrating
1503+ p' <- case h of
1504+ HydrationMode_Hydrating -> pure parent
1505+ HydrationMode_Immediate -> toNode <$> createDocumentFragment doc
6511506 unreadyChildren <- liftIO $ newIORef 0
6521507 let commitAction = do
6531508 c <- liftIO $ readIORef currentCohort
6541509 when (c <= cohortId) $ do -- If a newer cohort has already been committed, just ignore this
655− deleteBetweenExclusive before after
656− insertBefore df after
1510+ !before' <- liftIO $ readIORef before
1511+ !after' <- liftIO $ readIORef after
1512+ deleteBetweenExclusive before' after'
1513+ insertBefore p' after'
6571514 liftIO $ writeIORef currentCohort cohortId
6581515 myCommitAction
659− result <- runReaderT (unImmediateDomBuilderT child) $ initialEnv
660− { _immediateDomBuilderEnv_parent = toNode df
661− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
662− , _immediateDomBuilderEnv_commitAction = commitAction
663− }
1516+ delayed <- case h of
1517+ HydrationMode_Hydrating -> liftIO $ newIORef $ pure ()
1518+ HydrationMode_Immediate -> pure $ _hydrationDomBuilderEnv_delayed initialEnv
1519+ result <- runReaderT (unHydrationDomBuilderT child) $ initialEnv
1520+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1521+ , _hydrationDomBuilderEnv_commitAction = case h of
1522+ HydrationMode_Hydrating -> myCommitAction
1523+ HydrationMode_Immediate -> commitAction
1524+ , _hydrationDomBuilderEnv_parent = Left p'
1525+ , _hydrationDomBuilderEnv_delayed = delayed
1526+ }
1527+ dom <- case h of
1528+ HydrationMode_Hydrating -> liftIO $ readIORef delayed
1529+ HydrationMode_Immediate -> pure $ pure ()
6641530 uc <- liftIO $ readIORef unreadyChildren
6651531 let commitActionToRunNow = if uc == 0
666− then Just commitAction
1532+ then Just $ commitAction
6671533 else Nothing -- A child will run it when unreadyChildren is decremented to 0
668− return (commitActionToRunNow, result)
669− requestDomAction_ $ fmapMaybe fst child'
1534+ actions = case h of
1535+ HydrationMode_Hydrating -> Left dom
1536+ HydrationMode_Immediate -> Right commitActionToRunNow
1537+ return (actions, result)
1538+ let (hydrate', commitAction) = fanEither $ fmap fst child'
1539+ addHydrationStepWithSetup (hold hydrate0 hydrate') $ \contents -> do
1540+ hydrateStart
1541+ join $ sample contents
1542+ hydrateEnd
1543+ requestDomAction_ $ fmapMaybe id commitAction
6701544 return (result0, snd <$> child')
1545+
6711546 {-# INLINABLE traverseIntMapWithKeyWithAdjust #-}
6721547 traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjust'
6731548 {-# INLINABLE traverseDMapWithKeyWithAdjust #-}
6741549 traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjust'
6751550 {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-}
6761551 traverseDMapWithKeyWithAdjustWithMove = do
677− let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do
678− let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState k))) a)
1552+ let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose (TraverseChild t m (Some k)) v')) old = do
1553+ let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState (Some k)))) a)
6791554 new k = PatchDMapWithMove.nodeInfoMapFromM $ \case
680− PatchDMapWithMove.From_Insert (Compose (_, _, sRef, _)) -> do
681− readIORef sRef >>= \case
1555+ PatchDMapWithMove.From_Insert (Compose (TraverseChild (Left _hydration) _)) -> return PatchDMapWithMove.From_Delete
1556+ PatchDMapWithMove.From_Insert (Compose (TraverseChild (Right immediate) _)) -> do
1557+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
6821558 ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete
6831559 ChildReadyState_Unready _ -> do
684− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
685− return $ PatchDMapWithMove.From_Insert $ Constant sRef
1560+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ This k
1561+ return $ PatchDMapWithMove.From_Insert $ Constant (_traverseChildImmediate_childReadyState immediate)
6861562 PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete
6871563 PatchDMapWithMove.From_Move fromKey -> return $ PatchDMapWithMove.From_Move fromKey
688− deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState k))) (ComposeMaybe k) a -> IO (Constant () a)
1564+ deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState (Some k)))) (ComposeMaybe k) a -> IO (Constant () a)
6891565 deleteOrMove _ (Pair (Constant sRef) (ComposeMaybe mToKey)) = do
690− writeIORef sRef $ ChildReadyState_Unready $ Some.This <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases
1566+ writeIORef sRef $ ChildReadyState_Unready $ This <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases
6911567 return $ Constant ()
6921568 p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p
6931569 _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old
6941570 return $ applyAlways p' old
695− hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness $ \placeholders lastPlaceholderRef (p_ :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) -> do
1571+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness $ \placeholders lastPlaceholder (p_ :: PatchDMapWithMove k (Compose (TraverseChild t m (Some k)) v')) -> do
6961572 let p = unPatchDMapWithMove p_
6971573 phsBefore <- liftIO $ readIORef placeholders
698− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
699− let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a)
1574+ let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a)
7001575 collectIfMoved k e = do
701− let mThisPlaceholder = Map.lookup (Some.This k) phsBefore -- Will be Nothing if this element wasn't present before
702− nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsBefore
1576+ let mThisPlaceholder = Map.lookup (This k) phsBefore -- Will be Nothing if this element wasn't present before
1577+ nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (This k) phsBefore
7031578 case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of
7041579 False -> do
7051580 mapM_ (`deleteUpTo` nextPlaceholder) mThisPlaceholder
@@ -707,13 +1582,22 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
7071582 True -> do
7081583 Constant <$> mapM (`collectUpTo` nextPlaceholder) mThisPlaceholder
7091584 collected <- DMap.traverseWithKey collectIfMoved p
710− let !phsAfter = fromMaybe phsBefore $ apply (weakenPatchDMapWithMoveWith (\(Compose (_, ph, _, _)) -> ph) p_) phsBefore --TODO: Don't recompute this
711− let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant () a)
1585+ let !phsAfter = fromMaybe phsBefore $ apply filtered phsBefore
1586+ weakened :: PatchMapWithMove (Some k) (Either (TraverseChildHydration t m) (TraverseChildImmediate (Some k)))
1587+ weakened = weakenPatchDMapWithMoveWith (_traverseChild_mode . getCompose) p_
1588+ filtered :: PatchMapWithMove (Some k) DOM.Text
1589+ filtered = PatchMapWithMove $ flip Map.mapMaybe (unPatchMapWithMove weakened) $ \(PatchMapWithMove.NodeInfo from to) -> flip PatchMapWithMove.NodeInfo to <$> case from of
1590+ PatchMapWithMove.From_Insert (Left _hydration) -> Nothing
1591+ PatchMapWithMove.From_Insert (Right immediate) -> Just $ PatchMapWithMove.From_Insert $ _traverseChildImmediate_placeholder immediate
1592+ PatchMapWithMove.From_Delete -> Just $ PatchMapWithMove.From_Delete
1593+ PatchMapWithMove.From_Move k -> Just $ PatchMapWithMove.From_Move k
1594+ let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (TraverseChild t m (Some k)) v') a -> JSM (Constant () a)
7121595 placeFragment k e = do
713− let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsAfter
1596+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (This k) phsAfter
7141597 case PatchDMapWithMove._nodeInfo_from e of
715− PatchDMapWithMove.From_Insert (Compose (df, _, _, _)) -> do
716− df `insertBefore` nextPlaceholder
1598+ PatchDMapWithMove.From_Insert (Compose (TraverseChild x _)) -> case x of
1599+ Left _ -> pure ()
1600+ Right immediate -> _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
7171601 PatchDMapWithMove.From_Delete -> do
7181602 return ()
7191603 PatchDMapWithMove.From_Move fromKey -> do
@@ -724,105 +1608,166 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
7241608 liftIO $ writeIORef placeholders $! phsAfter
7251609
7261610{-# INLINABLE traverseDMapWithKeyWithAdjust' #-}
727−traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, DMap.GCompare k) => (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> ImmediateDomBuilderT t m (DMap k v', Event t (PatchDMap k v'))
1611+traverseDMapWithKeyWithAdjust'
1612+ :: forall s t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadJSM m, PrimMonad m, DMap.GCompare k, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1613+ => (forall a. k a -> v a -> HydrationDomBuilderT s t m (v' a))
1614+ -> DMap k v
1615+ -> Event t (PatchDMap k v)
1616+ -> HydrationDomBuilderT s t m (DMap k v', Event t (PatchDMap k v'))
7281617traverseDMapWithKeyWithAdjust' = do
729− let updateChildUnreadiness (p :: PatchDMap k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do
730− let new :: forall a. k a -> ComposeMaybe (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState k))) a)
1618+ let updateChildUnreadiness (p :: PatchDMap k (Compose (TraverseChild t m (Some k)) v')) old = do
1619+ let new :: forall a. k a -> ComposeMaybe (Compose (TraverseChild t m (Some k)) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState (Some k)))) a)
7311620 new k (ComposeMaybe m) = ComposeMaybe <$> case m of
7321621 Nothing -> return Nothing
733− Just (Compose (_, _, sRef, _)) -> do
734− readIORef sRef >>= \case
1622+ Just (Compose (TraverseChild (Left _hydration) _)) -> pure Nothing
1623+ Just (Compose (TraverseChild (Right immediate) _)) -> do
1624+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
7351625 ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
7361626 ChildReadyState_Unready _ -> do
737− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
738− return $ Just $ Constant sRef
1627+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ This k
1628+ return $ Just $ Constant (_traverseChildImmediate_childReadyState immediate)
7391629 delete _ (Constant sRef) = do
7401630 writeIORef sRef $ ChildReadyState_Unready Nothing
7411631 return $ Constant ()
7421632 p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p
7431633 _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old
7441634 return $ applyAlways p' old
745− hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchDMap p) -> do
1635+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholder (PatchDMap patch) -> do
7461636 phs <- liftIO $ readIORef placeholders
747− forM_ (DMap.toList p) $ \(k :=> ComposeMaybe mv) -> do
748− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
749− let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phs
750− forM_ (Map.lookup (Some.This k) phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
751− forM_ mv $ \(Compose (df, _, _, _)) -> df `insertBefore` nextPlaceholder
752− liftIO $ writeIORef placeholders $! fromMaybe phs $ apply (weakenPatchDMapWith (\(Compose (_, ph, _, _)) -> ph) $ PatchDMap p) phs
1637+ forM_ (DMap.toList patch) $ \(k :=> ComposeMaybe mv) -> do
1638+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (This k) phs
1639+ -- Delete old node
1640+ forM_ (Map.lookup (This k) phs) $ \thisPlaceholder -> do
1641+ thisPlaceholder `deleteUpTo` nextPlaceholder
1642+ -- Insert new node
1643+ forM_ mv $ \(Compose (TraverseChild e _)) -> case e of
1644+ Left _hydration -> pure ()
1645+ Right immediate -> do
1646+ _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
1647+ let weakened :: PatchMap (Some k) (Either (TraverseChildHydration t m) (TraverseChildImmediate (Some k)))
1648+ weakened = weakenPatchDMapWith (_traverseChild_mode . getCompose) $ PatchDMap patch
1649+ filtered :: PatchMap (Some k) DOM.Text
1650+ filtered = PatchMap $ flip Map.mapMaybe (unPatchMap weakened) $ \case
1651+ Nothing -> Just Nothing -- deletion
1652+ Just (Left _) -> Nothing
1653+ Just (Right immediate) -> Just $ Just $ _traverseChildImmediate_placeholder immediate
1654+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply filtered phs
7531655
7541656{-# INLINABLE traverseIntMapWithKeyWithAdjust' #-}
755−traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m) => (IntMap.Key -> v -> ImmediateDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> ImmediateDomBuilderT t m (IntMap v', Event t (PatchIntMap v'))
1657+traverseIntMapWithKeyWithAdjust'
1658+ :: forall s t m v v'. (Adjustable t m, MonadJSM m, MonadFix m, PrimMonad m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1659+ => (IntMap.Key -> v -> HydrationDomBuilderT s t m v')
1660+ -> IntMap v
1661+ -> Event t (PatchIntMap v)
1662+ -> HydrationDomBuilderT s t m (IntMap v', Event t (PatchIntMap v'))
7561663traverseIntMapWithKeyWithAdjust' = do
757− let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) old = do
758− let new :: IntMap.Key -> Maybe (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IO (Maybe (IORef ChildReadyStateInt))
1664+ let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (TraverseChild t m Int v')) old = do
1665+ let new :: IntMap.Key -> Maybe (TraverseChild t m Int v') -> IO (Maybe (IORef (ChildReadyState Int)))
7591666 new k m = case m of
7601667 Nothing -> return Nothing
761− Just (_, _, sRef, _) -> do
1668+ Just (TraverseChild (Left _hydration) _) -> pure Nothing
1669+ Just (TraverseChild (Right immediate) _) -> do
1670+ let sRef = _traverseChildImmediate_childReadyState immediate
7621671 readIORef sRef >>= \case
763− ChildReadyStateInt_Ready -> return Nothing -- Delete this child, since it's ready
764− ChildReadyStateInt_Unready _ -> do
765− writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
1672+ ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
1673+ ChildReadyState_Unready _ -> do
1674+ writeIORef sRef $ ChildReadyState_Unready $ Just k
7661675 return $ Just sRef
7671676 delete _ sRef = do
768− writeIORef sRef $ ChildReadyStateInt_Unready Nothing
1677+ writeIORef sRef $ ChildReadyState_Unready Nothing
7691678 return ()
7701679 p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner
7711680 _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old
7721681 return $ applyAlways p' old
773− hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchIntMap p) -> do
1682+ hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholder (PatchIntMap p) -> do
7741683 phs <- liftIO $ readIORef placeholders
7751684 forM_ (IntMap.toList p) $ \(k, mv) -> do
776− lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
7771685 let nextPlaceholder = maybe lastPlaceholder snd $ IntMap.lookupGT k phs
1686+ -- Delete old node
7781687 forM_ (IntMap.lookup k phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
779− forM_ mv $ \(df, _, _, _) -> df `insertBefore` nextPlaceholder
780− liftIO $ writeIORef placeholders $! fromMaybe phs $ apply ((\(_, ph, _, _) -> ph) <$> PatchIntMap p) phs
1688+ -- Insert new node
1689+ forM_ mv $ \(TraverseChild e _) -> case e of
1690+ Left _hydration -> pure ()
1691+ Right immediate -> do
1692+ _traverseChildImmediate_fragment immediate `insertBefore` nextPlaceholder
1693+ let filtered :: PatchIntMap DOM.Text
1694+ filtered = PatchIntMap $ flip IntMap.mapMaybe p $ \case
1695+ Nothing -> Just Nothing -- deletion
1696+ Just tc
1697+ | Right immediate <- _traverseChild_mode tc -> Just $ Just $ _traverseChildImmediate_placeholder immediate
1698+ | otherwise -> Nothing
1699+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply filtered phs
1700+
1701+{-# SPECIALIZE traverseIntMapWithKeyWithAdjust'
1702+ :: (IntMap.Key -> v -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM v')
1703+ -> IntMap v
1704+ -> Event Spider (PatchIntMap v)
1705+ -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM (IntMap v', Event Spider (PatchIntMap v'))
1706+ #-}
7811707
782−#if MIN_VERSION_base(4,9,0)
783−data ChildReadyState k
784−#else
785−data ChildReadyState (k :: * -> *)
786−#endif
1708+{-# SPECIALIZE traverseIntMapWithKeyWithAdjust'
1709+ :: (IntMap.Key -> v -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM v')
1710+ -> IntMap v
1711+ -> Event Spider (PatchIntMap v)
1712+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM (IntMap v', Event Spider (PatchIntMap v'))
1713+ #-}
1714+
1715+data ChildReadyState a
7871716 = ChildReadyState_Ready
788− | ChildReadyState_Unready !(Maybe (Some k))
1717+ | ChildReadyState_Unready !(Maybe a)
7891718 deriving (Show, Read, Eq, Ord)
7901719
791−data ChildReadyStateInt
792− = ChildReadyStateInt_Ready
793− | ChildReadyStateInt_Unready !(Maybe Int)
794− deriving (Show, Read, Eq, Ord)
1720+insertAfterPreviousNode :: (Monad m, MonadJSM m) => DOM.IsNode node => node -> HydrationRunnerT t m ()
1721+insertAfterPreviousNode node = do
1722+ parent <- askParent
1723+ nextNode <- maybe (Node.getFirstChild parent) Node.getNextSibling =<< getPreviousNode
1724+ Node.insertBefore_ parent node nextNode
1725+ setPreviousNode $ Just $ toNode node
7951726
796−{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
797−hoistTraverseIntMapWithKeyWithAdjust :: forall v v' t m p.
1727+{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
1728+hoistTraverseWithKeyWithAdjust
1729+ ::
7981730 ( Adjustable t m
1731+ , MonadHold t m
1732+ , DMap.GCompare k
7991733 , MonadIO m
8001734 , MonadJSM m
801− , MonadFix m
8021735 , PrimMonad m
803− , Monoid (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
804− , Functor p
1736+ , MonadFix m
1737+ , Patch (p k v)
1738+ , Patch (p k (Constant Int))
1739+ , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int)
1740+ , Patch (p k (Compose (TraverseChild t m (Some k)) v'))
1741+ , PatchTarget (p k (Compose (TraverseChild t m (Some k)) v')) ~ DMap k (Compose (TraverseChild t m (Some k)) v')
1742+ , Monoid (p k (Compose (TraverseChild t m (Some k)) v'))
1743+ , RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document
8051744 )
806− => ( (IntMap.Key -> v -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
807− -> IntMap v
808− -> Event t (p v)
809− -> RequesterT t JSM Identity (TriggerEventT t m) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event t (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')))
810− ) -- ^ The base monad's traversal
811− -> (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
812− -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ()) -- ^ Apply a patch to the DOM
813− -> (IntMap.Key -> v -> ImmediateDomBuilderT t m v')
814− -> IntMap v
815− -> Event t (p v)
816− -> ImmediateDomBuilderT t m (IntMap v', Event t (p v'))
817−hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
818− initialEnv <- ImmediateDomBuilderT ask
819− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
820− pendingChange :: IORef (IntMap (IORef ChildReadyStateInt), p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) <- liftIO $ newIORef mempty
1745+ => (forall vv vv'.
1746+ (forall a. k a -> vv a -> DomRenderHookT t m (vv' a))
1747+ -> DMap k vv
1748+ -> Event t (p k vv)
1749+ -> DomRenderHookT t m (DMap k vv', Event t (p k vv')))
1750+ -- ^ The base monad's traversal
1751+ -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv')
1752+ -- ^ A way of mapping over the patch type
1753+ -> (p k (Compose (TraverseChild t m (Some k)) v') -> DMap k (Constant (IORef (ChildReadyState (Some k)))) -> IO (DMap k (Constant (IORef (ChildReadyState (Some k))))))
1754+ -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
1755+ -> (IORef (Map.Map (Some k) DOM.Text) -> DOM.Text -> p k (Compose (TraverseChild t m (Some k)) v') -> JSM ())
1756+ -- ^ Apply a patch to the DOM
1757+ -> (forall a. k a -> v a -> HydrationDomBuilderT s t m (v' a))
1758+ -> DMap k v
1759+ -> Event t (p k v)
1760+ -> HydrationDomBuilderT s t m (DMap k v', Event t (p k v'))
1761+hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
1762+ doc <- askDocument
1763+ initialEnv <- HydrationDomBuilderT ask
1764+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
1765+ pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState (Some k)))), p k (Compose (TraverseChild t m (Some k)) v')) <- liftIO $ newIORef mempty
8211766 haveEverBeenReady <- liftIO $ newIORef False
822− placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
823− lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1767+ placeholders <- liftIO $ newIORef Map.empty
1768+ lastPlaceholder <- createTextNode doc ("" :: Text)
8241769 let applyDomUpdate p = do
825− applyDomUpdate_ placeholders lastPlaceholderRef p
1770+ applyDomUpdate_ placeholders lastPlaceholder p
8261771 markSelfReady
8271772 liftIO $ writeIORef pendingChange $! mempty
8281773 markSelfReady = do
@@ -833,86 +1778,105 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_
8331778 old <- liftIO $ readIORef parentUnreadyChildren
8341779 let new = pred old
8351780 liftIO $ writeIORef parentUnreadyChildren $! new
836− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
837− markChildReady :: IORef ChildReadyStateInt -> JSM ()
1781+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
1782+ markChildReady :: IORef (ChildReadyState (Some k)) -> JSM ()
8381783 markChildReady childReadyState = do
8391784 liftIO (readIORef childReadyState) >>= \case
840− ChildReadyStateInt_Ready -> return ()
841− ChildReadyStateInt_Unready countedAt -> do
842− liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
1785+ ChildReadyState_Ready -> return ()
1786+ ChildReadyState_Unready countedAt -> do
1787+ liftIO $ writeIORef childReadyState ChildReadyState_Ready
8431788 case countedAt of
8441789 Nothing -> return ()
845− Just k -> do -- This child has been counted as unready, so we need to remove it from the unready set
1790+ Just (This k) -> do -- This child has been counted as unready, so we need to remove it from the unready set
8461791 (oldUnready, p) <- liftIO $ readIORef pendingChange
847− when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
848− let newUnready = IntMap.delete k oldUnready
1792+ when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
1793+ let newUnready = DMap.delete k oldUnready
8491794 liftIO $ writeIORef pendingChange (newUnready, p)
850− when (IntMap.null newUnready) $ do
1795+ when (DMap.null newUnready) $ do
8511796 applyDomUpdate p
852− (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
853− let processChild k (_, _, sRef, _) = do
854− readIORef sRef >>= \case
855− ChildReadyStateInt_Ready -> return Nothing
856− ChildReadyStateInt_Unready _ -> do
857− writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
858− return $ Just sRef
859− initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
860− liftIO $ if IntMap.null initialUnready
1797+ (children0 :: DMap k (Compose (TraverseChild t m (Some k)) v'), children' :: Event t (p k (Compose (TraverseChild t m (Some k)) v')))
1798+ <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
1799+ let processChild k (Compose (TraverseChild e _)) = case e of
1800+ Left _ -> pure $ ComposeMaybe Nothing
1801+ Right immediate -> ComposeMaybe <$> do
1802+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
1803+ ChildReadyState_Ready -> return Nothing
1804+ ChildReadyState_Unready _ -> do
1805+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just $ This k
1806+ return $ Just $ Constant (_traverseChildImmediate_childReadyState immediate)
1807+ initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
1808+ liftIO $ if DMap.null initialUnready
8611809 then writeIORef haveEverBeenReady True
8621810 else do
8631811 modifyIORef' parentUnreadyChildren succ
8641812 writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
865− let result0 = IntMap.map (\(_, _, _, v) -> v) children0
866− placeholders0 = fmap (\(_, ph, _, _) -> ph) children0
867− result' = ffor children' $ fmap $ \(_, _, _, r) -> r
868− liftIO $ writeIORef placeholders $! placeholders0
869− _ <- IntMap.traverseWithKey (\_ (df, _, _, _) -> void $ append $ toNode df) children0
870− liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1813+ getHydrationMode >>= \case
1814+ HydrationMode_Hydrating -> addHydrationStepWithSetup (holdIncremental children0 children') $ \children -> do
1815+ dm :: DMap k (Compose (TraverseChild t m (Some k)) v') <- sample $ currentIncremental children
1816+ phs <- traverse id $ weakenDMapWith (either _traverseChildHydration_delayed (pure . _traverseChildImmediate_placeholder) . _traverseChild_mode . getCompose) dm
1817+ liftIO $ writeIORef placeholders $! phs
1818+ insertAfterPreviousNode lastPlaceholder
1819+ HydrationMode_Immediate -> do
1820+ let activate i = do
1821+ append $ toNode $ _traverseChildImmediate_fragment i
1822+ pure $ _traverseChildImmediate_placeholder i
1823+ phs <- traverse id $ weakenDMapWith (either (error "impossible") activate . _traverseChild_mode . getCompose) children0
1824+ liftIO $ writeIORef placeholders $! phs
1825+ append $ toNode lastPlaceholder
8711826 requestDomAction_ $ ffor children' $ \p -> do
8721827 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
8731828 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
8741829 let !newP = p <> oldP
8751830 liftIO $ writeIORef pendingChange (newUnready, newP)
876− when (IntMap.null newUnready) $ do
1831+ when (DMap.null newUnready) $ do
8771832 applyDomUpdate newP
1833+ let result0 = DMap.map (_traverseChild_result . getCompose) children0
1834+ result' = ffor children' $ mapPatch $ _traverseChild_result . getCompose
8781835 return (result0, result')
8791836
880−{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
881−hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
1837+{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
1838+hoistTraverseIntMapWithKeyWithAdjust ::
8821839 ( Adjustable t m
8831840 , MonadHold t m
884− , DMap.GCompare k
885− , MonadIO m
8861841 , MonadJSM m
887− , PrimMonad m
8881842 , MonadFix m
889− , Patch (p k v)
890− , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int)
891− , Monoid (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v'))
892− , Patch (p k (Constant Int))
1843+ , PrimMonad m
1844+ , Monoid (p (TraverseChild t m Int v'))
1845+ , Functor p
1846+ , PatchTarget (p (HydrationRunnerT t m ())) ~ IntMap (HydrationRunnerT t m ())
1847+ , PatchTarget (p (TraverseChild t m Int v')) ~ IntMap (TraverseChild t m Int v')
1848+ , Patch (p (HydrationRunnerT t m ()))
1849+ , Patch (p (TraverseChild t m Int v'))
1850+ , RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document
8931851 )
894− => (forall vv vv'.
895− (forall a. k a -> vv a -> RequesterT t JSM Identity (TriggerEventT t m) (vv' a))
896− -> DMap k vv
897− -> Event t (p k vv)
898− -> RequesterT t JSM Identity (TriggerEventT t m) (DMap k vv', Event t (p k vv'))
899− ) -- ^ The base monad's traversal
900− -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv') -- ^ A way of mapping over the patch type
901− -> (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> DMap k (Constant (IORef (ChildReadyState k))) -> IO (DMap k (Constant (IORef (ChildReadyState k))))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
902− -> (IORef (Map.Map (Some.Some k) DOM.Text) -> IORef DOM.Text -> p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> JSM ()) -- ^ Apply a patch to the DOM
903− -> (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a))
904− -> DMap k v
905− -> Event t (p k v)
906− -> ImmediateDomBuilderT t m (DMap k v', Event t (p k v'))
907−hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ (f :: forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) (dm0 :: DMap k v) dm' = do
908− initialEnv <- ImmediateDomBuilderT ask
909− let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
910− pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState k))), p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) <- liftIO $ newIORef mempty
1852+ => ((IntMap.Key -> v -> DomRenderHookT t m (TraverseChild t m Int v'))
1853+ -> IntMap v
1854+ -> Event t (p v)
1855+ -> DomRenderHookT t m (IntMap (TraverseChild t m Int v'), Event t (p (TraverseChild t m Int v'))))
1856+ -- ^ The base monad's traversal
1857+ -> (p (TraverseChild t m Int v')
1858+ -> IntMap (IORef (ChildReadyState Int))
1859+ -> IO (IntMap (IORef (ChildReadyState Int))))
1860+ -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
1861+ -> (IORef (IntMap DOM.Text)
1862+ -> DOM.Text
1863+ -> p (TraverseChild t m Int v')
1864+ -> JSM ())
1865+ -- ^ Apply a patch to the DOM
1866+ -> (IntMap.Key -> v -> HydrationDomBuilderT s t m v')
1867+ -> IntMap v
1868+ -> Event t (p v)
1869+ -> HydrationDomBuilderT s t m (IntMap v', Event t (p v'))
1870+hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
1871+ doc <- askDocument
1872+ initialEnv <- HydrationDomBuilderT ask
1873+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
1874+ pendingChange :: IORef (IntMap (IORef (ChildReadyState Int)), p (TraverseChild t m Int v')) <- liftIO $ newIORef mempty
9111875 haveEverBeenReady <- liftIO $ newIORef False
912− placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
913− lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1876+ placeholders <- liftIO $ newIORef IntMap.empty
1877+ lastPlaceholder <- createTextNode doc ("" :: Text)
9141878 let applyDomUpdate p = do
915− applyDomUpdate_ placeholders lastPlaceholderRef p
1879+ applyDomUpdate_ placeholders lastPlaceholder p
9161880 markSelfReady
9171881 liftIO $ writeIORef pendingChange $! mempty
9181882 markSelfReady = do
@@ -923,8 +1887,8 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda
9231887 old <- liftIO $ readIORef parentUnreadyChildren
9241888 let new = pred old
9251889 liftIO $ writeIORef parentUnreadyChildren $! new
926− when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
927− markChildReady :: IORef (ChildReadyState k) -> JSM ()
1890+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
1891+ markChildReady :: IORef (ChildReadyState Int) -> JSM ()
9281892 markChildReady childReadyState = do
9291893 liftIO (readIORef childReadyState) >>= \case
9301894 ChildReadyState_Ready -> return ()
@@ -932,82 +1896,191 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda
9321896 liftIO $ writeIORef childReadyState ChildReadyState_Ready
9331897 case countedAt of
9341898 Nothing -> return ()
935− Just (Some.This k) -> do -- This child has been counted as unready, so we need to remove it from the unready set
1899+ Just k -> do -- This child has been counted as unready, so we need to remove it from the unready set
9361900 (oldUnready, p) <- liftIO $ readIORef pendingChange
937− when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
938− let newUnready = DMap.delete k oldUnready
1901+ when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
1902+ let newUnready = IntMap.delete k oldUnready
9391903 liftIO $ writeIORef pendingChange (newUnready, p)
940− when (DMap.null newUnready) $ do
1904+ when (IntMap.null newUnready) $ do
9411905 applyDomUpdate p
942− (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
943− let processChild k (Compose (_, _, sRef, _)) = ComposeMaybe <$> do
944− readIORef sRef >>= \case
945− ChildReadyState_Ready -> return Nothing
946− ChildReadyState_Unready _ -> do
947− writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
948− return $ Just $ Constant sRef
949− initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
950− liftIO $ if DMap.null initialUnready
1906+ (children0 :: IntMap (TraverseChild t m Int v'), children' :: Event t (p (TraverseChild t m Int v')))
1907+ <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
1908+ let processChild k (TraverseChild e _) = case e of
1909+ Left _ -> pure Nothing
1910+ Right immediate -> do
1911+ readIORef (_traverseChildImmediate_childReadyState immediate) >>= \case
1912+ ChildReadyState_Ready -> return Nothing
1913+ ChildReadyState_Unready _ -> do
1914+ writeIORef (_traverseChildImmediate_childReadyState immediate) $ ChildReadyState_Unready $ Just k
1915+ return $ Just (_traverseChildImmediate_childReadyState immediate)
1916+ initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
1917+ liftIO $ if IntMap.null initialUnready
9511918 then writeIORef haveEverBeenReady True
9521919 else do
9531920 modifyIORef' parentUnreadyChildren succ
9541921 writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
955− let result0 = DMap.map (\(Compose (_, _, _, v)) -> v) children0
956− placeholders0 = weakenDMapWith (\(Compose (_, ph, _, _)) -> ph) children0
957− result' = ffor children' $ mapPatch $ \(Compose (_, _, _, r)) -> r
958− liftIO $ writeIORef placeholders $! placeholders0
959− _ <- DMap.traverseWithKey (\_ (Compose (df, _, _, _)) -> Constant () <$ append (toNode df)) children0
960− liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1922+ getHydrationMode >>= \case
1923+ HydrationMode_Hydrating -> addHydrationStepWithSetup (holdIncremental children0 children') $ \children -> do
1924+ dm :: IntMap (TraverseChild t m Int v') <- sample $ currentIncremental children
1925+ phs <- traverse (either _traverseChildHydration_delayed (pure . _traverseChildImmediate_placeholder) . _traverseChild_mode) dm
1926+ liftIO $ writeIORef placeholders $! phs
1927+ insertAfterPreviousNode lastPlaceholder
1928+ HydrationMode_Immediate -> do
1929+ let activate i = do
1930+ append $ toNode $ _traverseChildImmediate_fragment i
1931+ pure $ _traverseChildImmediate_placeholder i
1932+ phs <- traverse (either (error "impossible") activate . _traverseChild_mode) children0
1933+ liftIO $ writeIORef placeholders $! phs
1934+ append $ toNode lastPlaceholder
9611935 requestDomAction_ $ ffor children' $ \p -> do
9621936 (oldUnready, oldP) <- liftIO $ readIORef pendingChange
9631937 newUnready <- liftIO $ updateChildUnreadiness p oldUnready
9641938 let !newP = p <> oldP
9651939 liftIO $ writeIORef pendingChange (newUnready, newP)
966− when (DMap.null newUnready) $ do
1940+ when (IntMap.null newUnready) $ do
9671941 applyDomUpdate newP
1942+ let result0 = IntMap.map _traverseChild_result children0
1943+ result' = ffor children' $ fmap $ _traverseChild_result
9681944 return (result0, result')
9691945
1946+{-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust
1947+ :: ((IntMap.Key -> v -> DomRenderHookT Spider HydrationM (TraverseChild Spider HydrationM Int v'))
1948+ -> IntMap v
1949+ -> Event Spider (PatchIntMap v)
1950+ -> DomRenderHookT Spider HydrationM (IntMap (TraverseChild Spider HydrationM Int v'), Event Spider (PatchIntMap (TraverseChild Spider HydrationM Int v'))))
1951+ -> (PatchIntMap (TraverseChild Spider HydrationM Int v')
1952+ -> IntMap (IORef (ChildReadyState Int))
1953+ -> IO (IntMap (IORef (ChildReadyState Int))))
1954+ -> (IORef (IntMap DOM.Text)
1955+ -> DOM.Text
1956+ -> PatchIntMap (TraverseChild Spider HydrationM Int v')
1957+ -> JSM ())
1958+ -> (IntMap.Key -> v -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM v')
1959+ -> IntMap v
1960+ -> Event Spider (PatchIntMap v)
1961+ -> HydrationDomBuilderT HydrationDomSpace Spider HydrationM (IntMap v', Event Spider (PatchIntMap v'))
1962+ #-}
1963+
1964+{-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust
1965+ :: ((IntMap.Key -> v -> DomRenderHookT Spider HydrationM (TraverseChild Spider HydrationM Int v'))
1966+ -> IntMap v
1967+ -> Event Spider (PatchIntMap v)
1968+ -> DomRenderHookT Spider HydrationM (IntMap (TraverseChild Spider HydrationM Int v'), Event Spider (PatchIntMap (TraverseChild Spider HydrationM Int v'))))
1969+ -> (PatchIntMap (TraverseChild Spider HydrationM Int v')
1970+ -> IntMap (IORef (ChildReadyState Int))
1971+ -> IO (IntMap (IORef (ChildReadyState Int))))
1972+ -> (IORef (IntMap DOM.Text)
1973+ -> DOM.Text
1974+ -> PatchIntMap (TraverseChild Spider HydrationM Int v')
1975+ -> JSM ())
1976+ -> (IntMap.Key -> v -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM v')
1977+ -> IntMap v
1978+ -> Event Spider (PatchIntMap v)
1979+ -> HydrationDomBuilderT GhcjsDomSpace Spider HydrationM (IntMap v', Event Spider (PatchIntMap v'))
1980+ #-}
1981+
1982+data TraverseChildImmediate k = TraverseChildImmediate
1983+ { _traverseChildImmediate_fragment :: {-# UNPACK #-} !DOM.DocumentFragment
1984+ -- ^ Child is appended to this fragment
1985+ , _traverseChildImmediate_placeholder :: {-# UNPACK #-} !DOM.Text
1986+ -- ^ Placeholder reference
1987+ , _traverseChildImmediate_childReadyState :: {-# UNPACK #-} !(IORef (ChildReadyState k))
1988+ }
1989+
1990+newtype TraverseChildHydration t m = TraverseChildHydration
1991+ { _traverseChildHydration_delayed :: HydrationRunnerT t m DOM.Text
1992+ -- ^ Action to run at switchover, returns the placeholder
1993+ }
1994+
1995+data TraverseChild t m k a = TraverseChild
1996+ { _traverseChild_mode :: !(Either (TraverseChildHydration t m) (TraverseChildImmediate k))
1997+ , _traverseChild_result :: !a
1998+ } deriving Functor
1999+
9702000{-# INLINABLE drawChildUpdate #-}
971−drawChildUpdate :: (MonadIO m, MonadJSM m)
972− => ImmediateDomBuilderEnv t
2001+drawChildUpdate :: (MonadJSM m, Reflex t)
2002+ => HydrationDomBuilderEnv t m
9732003 -> (IORef (ChildReadyState k) -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
974− -> ImmediateDomBuilderT t m (v' a)
975− -> RequesterT t JSM Identity (TriggerEventT t m) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a)
2004+ -> HydrationDomBuilderT s t m (f a)
2005+ -> DomRenderHookT t m (Compose (TraverseChild t m k) f a)
9762006drawChildUpdate initialEnv markReady child = do
977− childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing
2007+ let doc = _hydrationDomBuilderEnv_document initialEnv
9782008 unreadyChildren <- liftIO $ newIORef 0
979− df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
980− (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
981− { _immediateDomBuilderEnv_parent = toNode df
982− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
983− , _immediateDomBuilderEnv_commitAction = markReady childReadyState
984− }
985− u <- liftIO $ readIORef unreadyChildren
986− when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
987− return $ Compose (df, placeholder, childReadyState, result)
2009+ liftIO (readIORef $ _hydrationDomBuilderEnv_hydrationMode initialEnv) >>= \case
2010+ HydrationMode_Hydrating -> do
2011+ childDelayedRef <- liftIO $ newIORef $ pure ()
2012+ result <- runReaderT (unHydrationDomBuilderT child) initialEnv
2013+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
2014+ , _hydrationDomBuilderEnv_delayed = childDelayedRef
2015+ }
2016+ childDelayed <- liftIO $ readIORef childDelayedRef
2017+ return $ Compose $ TraverseChild
2018+ { _traverseChild_result = result
2019+ , _traverseChild_mode = Left TraverseChildHydration
2020+ { _traverseChildHydration_delayed = do
2021+ placeholder <- createTextNode doc ("" :: Text)
2022+ insertAfterPreviousNode placeholder
2023+ childDelayed
2024+ pure placeholder
2025+ }
2026+ }
2027+ HydrationMode_Immediate -> do
2028+ childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing
2029+ df <- createDocumentFragment doc
2030+ placeholder <- createTextNode doc ("" :: Text)
2031+ Node.appendChild_ df placeholder
2032+ result <- runReaderT (unHydrationDomBuilderT child) initialEnv
2033+ { _hydrationDomBuilderEnv_parent = Left $ toNode df
2034+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
2035+ , _hydrationDomBuilderEnv_commitAction = markReady childReadyState
2036+ }
2037+ u <- liftIO $ readIORef unreadyChildren
2038+ when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
2039+ return $ Compose $ TraverseChild
2040+ { _traverseChild_result = result
2041+ , _traverseChild_mode = Right TraverseChildImmediate
2042+ { _traverseChildImmediate_fragment = df
2043+ , _traverseChildImmediate_placeholder = placeholder
2044+ , _traverseChildImmediate_childReadyState = childReadyState
2045+ }
2046+ }
9882047
989−{-# INLINABLE drawChildUpdateInt #-}
990−drawChildUpdateInt :: (MonadIO m, MonadJSM m)
991− => ImmediateDomBuilderEnv t
992− -> (IORef ChildReadyStateInt -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
993− -> ImmediateDomBuilderT t m v'
994− -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')
995−drawChildUpdateInt initialEnv markReady child = do
996− childReadyState <- liftIO $ newIORef $ ChildReadyStateInt_Unready Nothing
997− unreadyChildren <- liftIO $ newIORef 0
998− df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
999− (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
1000− { _immediateDomBuilderEnv_parent = toNode df
1001− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
1002− , _immediateDomBuilderEnv_commitAction = markReady childReadyState
1003− }
1004− u <- liftIO $ readIORef unreadyChildren
1005− when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
1006− return (df, placeholder, childReadyState, result)
2048+{-# SPECIALIZE drawChildUpdate
2049+ :: HydrationDomBuilderEnv Spider HydrationM
2050+ -> (IORef (ChildReadyState Int) -> JSM ())
2051+ -> HydrationDomBuilderT s Spider HydrationM (Identity a)
2052+ -> DomRenderHookT Spider HydrationM (Compose (TraverseChild Spider HydrationM Int) Identity a)
2053+ #-}
2054+
2055+{-# SPECIALIZE drawChildUpdate
2056+ :: HydrationDomBuilderEnv Spider HydrationM
2057+ -> (IORef (ChildReadyState (Some k)) -> JSM ())
2058+ -> HydrationDomBuilderT s Spider HydrationM (f a)
2059+ -> DomRenderHookT Spider HydrationM (Compose (TraverseChild Spider HydrationM (Some k)) f a)
2060+ #-}
10072061
1008−mkHasFocus :: (MonadHold t m, Reflex t) => Element er d t -> m (Dynamic t Bool)
2062+{-# INLINABLE drawChildUpdateInt #-}
2063+drawChildUpdateInt :: (MonadIO m, MonadJSM m, Reflex t)
2064+ => HydrationDomBuilderEnv t m
2065+ -> (IORef (ChildReadyState k) -> JSM ())
2066+ -> HydrationDomBuilderT s t m v
2067+ -> DomRenderHookT t m (TraverseChild t m k v)
2068+drawChildUpdateInt env mark m = fmap runIdentity . getCompose <$> drawChildUpdate env mark (Identity <$> m)
2069+
2070+{-# SPECIALIZE drawChildUpdateInt
2071+ :: HydrationDomBuilderEnv Spider HydrationM
2072+ -> (IORef (ChildReadyState k) -> JSM ())
2073+ -> HydrationDomBuilderT s Spider HydrationM v
2074+ -> DomRenderHookT Spider HydrationM (TraverseChild Spider HydrationM k v)
2075+ #-}
2076+
2077+{-# INLINE mkHasFocus #-}
2078+mkHasFocus
2079+ :: (HasDocument m, MonadJSM m, IsNode (RawElement d), MonadHold t m, Reflex t, DOM.IsDocumentOrShadowRoot (RawDocument (DomBuilderSpace m)))
2080+ => Element er d t -> m (Dynamic t Bool)
10092081mkHasFocus e = do
1010− let initialFocus = False --TODO: Actually get the initial focus of the element
2082+ doc <- askDocument
2083+ initialFocus <- Node.isSameNode (toNode $ _element_raw e) . fmap toNode =<< Document.getActiveElement doc
10112084 holdDyn initialFocus $ leftmost
10122085 [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
10132086 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
@@ -1016,39 +2089,49 @@ mkHasFocus e = do
10162089insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
10172090insertBefore new existing = do
10182091 p <- getParentNodeUnchecked existing
1019− DOM.insertBefore_ p new (Just existing) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
2092+ Node.insertBefore_ p new (Just existing) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
10202093
1021−instance PerformEvent t m => PerformEvent t (ImmediateDomBuilderT t m) where
1022− type Performable (ImmediateDomBuilderT t m) = Performable m
2094+type ImmediateDomBuilderT = HydrationDomBuilderT GhcjsDomSpace
2095+
2096+instance PerformEvent t m => PerformEvent t (HydrationDomBuilderT s t m) where
2097+ type Performable (HydrationDomBuilderT s t m) = Performable m
10232098 {-# INLINABLE performEvent_ #-}
10242099 performEvent_ e = lift $ performEvent_ e
10252100 {-# INLINABLE performEvent #-}
10262101 performEvent e = lift $ performEvent e
10272102
1028−instance PostBuild t m => PostBuild t (ImmediateDomBuilderT t m) where
2103+instance PostBuild t m => PostBuild t (HydrationDomBuilderT s t m) where
10292104 {-# INLINABLE getPostBuild #-}
10302105 getPostBuild = lift getPostBuild
10312106
1032−instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (ImmediateDomBuilderT t m) where
2107+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydrationDomBuilderT s t m) where
10332108 {-# INLINABLE newEventWithTrigger #-}
10342109 newEventWithTrigger = lift . newEventWithTrigger
10352110 {-# INLINABLE newFanEventWithTrigger #-}
10362111 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
10372112
1038−instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (ImmediateDomBuilderT t m) where
2113+instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (HydrationDomBuilderT s t m) where
2114+ {-# INLINABLE newTriggerEvent #-}
2115+ newTriggerEvent = HydrationDomBuilderT . lift $ newTriggerEvent
2116+ {-# INLINABLE newTriggerEventWithOnComplete #-}
2117+ newTriggerEventWithOnComplete = HydrationDomBuilderT . lift $ newTriggerEventWithOnComplete
2118+ {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
2119+ newEventWithLazyTriggerWithOnComplete f = HydrationDomBuilderT . lift $ newEventWithLazyTriggerWithOnComplete f
2120+
2121+instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (DomRenderHookT t m) where
10392122 {-# INLINABLE newTriggerEvent #-}
1040− newTriggerEvent = ImmediateDomBuilderT . lift . lift $ newTriggerEvent
2123+ newTriggerEvent = DomRenderHookT . lift $ newTriggerEvent
10412124 {-# INLINABLE newTriggerEventWithOnComplete #-}
1042− newTriggerEventWithOnComplete = ImmediateDomBuilderT . lift . lift $ newTriggerEventWithOnComplete
2125+ newTriggerEventWithOnComplete = DomRenderHookT . lift $ newTriggerEventWithOnComplete
10432126 {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
1044− newEventWithLazyTriggerWithOnComplete f = ImmediateDomBuilderT . lift . lift $ newEventWithLazyTriggerWithOnComplete f
2127+ newEventWithLazyTriggerWithOnComplete f = DomRenderHookT . lift $ newEventWithLazyTriggerWithOnComplete f
10452128
1046−instance HasJSContext m => HasJSContext (ImmediateDomBuilderT t m) where
1047− type JSContextPhantom (ImmediateDomBuilderT t m) = JSContextPhantom m
2129+instance HasJSContext m => HasJSContext (HydrationDomBuilderT s t m) where
2130+ type JSContextPhantom (HydrationDomBuilderT s t m) = JSContextPhantom m
10482131 askJSContext = lift askJSContext
10492132
1050−instance MonadRef m => MonadRef (ImmediateDomBuilderT t m) where
1051− type Ref (ImmediateDomBuilderT t m) = Ref m
2133+instance MonadRef m => MonadRef (HydrationDomBuilderT s t m) where
2134+ type Ref (HydrationDomBuilderT s t m) = Ref m
10522135 {-# INLINABLE newRef #-}
10532136 newRef = lift . newRef
10542137 {-# INLINABLE readRef #-}
@@ -1056,12 +2139,12 @@ instance MonadRef m => MonadRef (ImmediateDomBuilderT t m) where
10562139 {-# INLINABLE writeRef #-}
10572140 writeRef r = lift . writeRef r
10582141
1059−instance MonadAtomicRef m => MonadAtomicRef (ImmediateDomBuilderT t m) where
2142+instance MonadAtomicRef m => MonadAtomicRef (HydrationDomBuilderT s t m) where
10602143 {-# INLINABLE atomicModifyRef #-}
10612144 atomicModifyRef r = lift . atomicModifyRef r
10622145
1063−instance (HasJS x m, ReflexHost t) => HasJS x (ImmediateDomBuilderT t m) where
1064− type JSX (ImmediateDomBuilderT t m) = JSX m
2146+instance (HasJS x m, ReflexHost t) => HasJS x (HydrationDomBuilderT s t m) where
2147+ type JSX (HydrationDomBuilderT s t m) = JSX m
10652148 liftJS = lift . liftJS
10662149
10672150type family EventType en where
@@ -1519,11 +2602,11 @@ getTouchEvent = do
15192602 , _touchEventResult_touches = touches
15202603 }
15212604
1522−instance MonadSample t m => MonadSample t (ImmediateDomBuilderT t m) where
2605+instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
15232606 {-# INLINABLE sample #-}
15242607 sample = lift . sample
15252608
1526−instance MonadHold t m => MonadHold t (ImmediateDomBuilderT t m) where
2609+instance MonadHold t m => MonadHold t (HydrationDomBuilderT s t m) where
15272610 {-# INLINABLE hold #-}
15282611 hold v0 v' = lift $ hold v0 v'
15292612 {-# INLINABLE holdDyn #-}
@@ -1545,7 +2628,7 @@ data Window t = Window
15452628 , _window_raw :: DOM.Window
15462629 }
15472630
1548−wrapWindow :: (MonadJSM m, MonadReflexCreateTrigger t m) => DOM.Window -> WindowConfig t -> ImmediateDomBuilderT t m (Window t)
2631+wrapWindow :: (MonadJSM m, MonadReflexCreateTrigger t m) => DOM.Window -> WindowConfig t -> HydrationDomBuilderT GhcjsDomSpace t m (Window t)
15492632wrapWindow wv _ = do
15502633 events <- wrapDomEventsMaybe wv (defaultDomWindowEventHandler wv) windowOnEventName
15512634 return $ Window
reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+71−4
@@ -15,6 +15,7 @@
1515{-# LANGUAGE UndecidableInstances #-}
1616module Reflex.Dom.Builder.Static where
1717
18+import Data.IORef (IORef)
1819import Blaze.ByteString.Builder.Html.Utf8
1920import Control.Lens hiding (element)
2021import Control.Monad.Exception
@@ -32,11 +33,13 @@ import qualified Data.Dependent.Map as DMap
3233import Data.Dependent.Sum (DSum (..))
3334import Data.Functor.Compose
3435import Data.Functor.Constant
36+import Data.IntMap (IntMap)
37+import qualified Data.IntMap as IntMap
3538import qualified Data.Map as Map
3639import Data.Map.Misc (applyMap)
37−import Data.Monoid
3840import qualified Data.Set as Set
3941import Data.Text (Text)
42+import qualified Data.Text as T
4043import Data.Text.Encoding
4144import Data.Tuple
4245import GHC.Generics
@@ -49,7 +52,9 @@ import Reflex.Host.Class
4952import Reflex.PerformEvent.Base
5053import Reflex.PerformEvent.Class
5154import Reflex.PostBuild.Base
55+import Reflex.PostBuild.Class
5256import Reflex.TriggerEvent.Class
57+import System.Random (randomRIO)
5358
5459data StaticDomBuilderEnv t = StaticDomBuilderEnv
5560 { _staticDomBuilderEnv_shouldEscape :: Bool
@@ -77,6 +82,10 @@ runStaticDomBuilderT (StaticDomBuilderT a) e = do
7782 (result, a') <- runStateT (runReaderT a e) []
7883 return (result, mconcat $ reverse a')
7984
85+instance PostBuild t m => PostBuild t (StaticDomBuilderT t m) where
86+ {-# INLINABLE getPostBuild #-}
87+ getPostBuild = lift getPostBuild
88+
8089instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (StaticDomBuilderT t m) where
8190 {-# INLINABLE newEventWithTrigger #-}
8291 newEventWithTrigger = lift . newEventWithTrigger
@@ -141,8 +150,8 @@ instance DomSpace StaticDomSpace where
141150 type EventSpec StaticDomSpace = StaticEventSpec
142151 type RawDocument StaticDomSpace = ()
143152 type RawTextNode StaticDomSpace = ()
153+ type RawCommentNode StaticDomSpace = ()
144154 type RawElement StaticDomSpace = ()
145− type RawFile StaticDomSpace = ()
146155 type RawInputElement StaticDomSpace = ()
147156 type RawTextAreaElement StaticDomSpace = ()
148157 type RawSelectElement StaticDomSpace = ()
@@ -151,21 +160,70 @@ instance DomSpace StaticDomSpace where
151160instance (SupportsStaticDomBuilder t m, Monad m) => HasDocument (StaticDomBuilderT t m) where
152161 askDocument = pure ()
153162
154−instance (Reflex t, Adjustable t m, MonadHold t m) => Adjustable t (StaticDomBuilderT t m) where
163+instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m) => Adjustable t (StaticDomBuilderT t m) where
155164 runWithReplace a0 a' = do
156165 e <- StaticDomBuilderT ask
166+ key <- replaceStart
157167 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
158168 o <- hold (snd result0) $ fmapCheap snd result'
159169 StaticDomBuilderT $ modify $ (:) $ join o
170+ replaceEnd key
160171 return (fst result0, fmapCheap fst result')
172+ traverseIntMapWithKeyWithAdjust = hoistIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust
161173 traverseDMapWithKeyWithAdjust = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap
162174 traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove
163175
176+-- TODO remove this?
177+replaceStart :: (DomBuilder t m, MonadIO m) => m Text
178+replaceStart = do
179+ str <- liftIO $ replicateM 8 $ randomRIO ('a', 'z')
180+ let key = "-" <> T.pack str
181+ _ <- commentNode $ def { _commentNodeConfig_initialContents = "replace-start" <> key }
182+ pure key
183+
184+replaceEnd :: DomBuilder t m => Text -> m ()
185+replaceEnd key = void $ commentNode $ def { _commentNodeConfig_initialContents = "replace-end" <> key }
186+
187+hoistIntMapWithKeyWithAdjust :: forall t m p a b.
188+ ( Adjustable t m
189+ , MonadHold t m
190+ , Patch (p a)
191+ , Functor p
192+ , Patch (p (Behavior t Builder))
193+ , PatchTarget (p (Behavior t Builder)) ~ IntMap (Behavior t Builder)
194+ , Ref m ~ IORef, MonadIO m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m -- TODO remove
195+ )
196+ => (forall x. (IntMap.Key -> a -> m x)
197+ -> IntMap a
198+ -> Event t (p a)
199+ -> m (IntMap x, Event t (p x))
200+ ) -- ^ The base monad's traversal
201+ -> (IntMap.Key -> a -> StaticDomBuilderT t m b)
202+ -> IntMap a
203+ -> Event t (p a)
204+ -> StaticDomBuilderT t m (IntMap b, Event t (p b))
205+hoistIntMapWithKeyWithAdjust base f im0 im' = do
206+ e <- StaticDomBuilderT ask
207+ (children0, children') <- lift $ base (\k v -> runStaticDomBuilderT (f k v) e) im0 im'
208+ let result0 = IntMap.map fst children0
209+ result' = (fmap . fmap) fst children'
210+ outputs0 :: IntMap (Behavior t Builder)
211+ outputs0 = IntMap.map snd children0
212+ outputs' :: Event t (p (Behavior t Builder))
213+ outputs' = (fmap . fmap) snd children'
214+ outputs <- holdIncremental outputs0 outputs'
215+ StaticDomBuilderT $ modify $ (:) $ pull $ do
216+ os <- sample $ currentIncremental outputs
217+ fmap mconcat $ forM (IntMap.toList os) $ \(_, o) -> do
218+ sample o
219+ return (result0, result')
220+
164221hoistDMapWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
165222 ( Adjustable t m
166223 , MonadHold t m
167224 , PatchTarget (p k (Constant (Behavior t Builder))) ~ DMap k (Constant (Behavior t Builder))
168225 , Patch (p k (Constant (Behavior t Builder)))
226+ , Ref m ~ IORef, MonadIO m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m -- TODO remove
169227 )
170228 => (forall vv vv'.
171229 (forall a. k a -> vv a -> m (vv' a))
@@ -210,6 +268,15 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
210268 Nothing -> return (pure (escape initialContents))
211269 Just setContents -> hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
212270 return $ TextNode ()
271+ {-# INLINABLE commentNode #-}
272+ commentNode (CommentNodeConfig initialContents mSetContents) = StaticDomBuilderT $ do
273+ --TODO: Do not escape quotation marks; see https://stackoverflow.com/questions/25612166/what-characters-must-be-escaped-in-html-5
274+ shouldEscape <- asks _staticDomBuilderEnv_shouldEscape
275+ let escape = if shouldEscape then fromHtmlEscapedText else byteString . encodeUtf8
276+ modify . (:) =<< (\c -> "<!--" <> c <> "-->") <$> case mSetContents of
277+ Nothing -> return (pure (escape initialContents))
278+ Just setContents -> hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
279+ return $ CommentNode ()
213280 {-# INLINABLE element #-}
214281 element elementTag cfg child = do
215282 -- https://www.w3.org/TR/html-markup/syntax.html#syntax-elements
@@ -220,7 +287,7 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
220287 StaticDomBuilderT $ do
221288 let shouldEscape = elementTag `Set.notMember` noEscapeElements
222289 (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape Nothing
223− attrs0 <- foldDyn applyMap (cfg ^. initialAttributes) (cfg ^. modifyAttributes)
290+ attrs0 <- foldDyn applyMap (Map.insert "data-ssr" "" $ cfg ^. initialAttributes) (cfg ^. modifyAttributes)
224291 selectValue <- asks _staticDomBuilderEnv_selectValue
225292 let addSelectedAttr attrs sel = case Map.lookup "value" attrs of
226293 Just v | v == sel -> attrs <> Map.singleton "selected" ""
reflex-dom-core/src/Reflex/Dom/Location.hs+39
@@ -12,6 +12,7 @@ module Reflex.Dom.Location
1212 , getLocationProtocol
1313 , getLocationUrl
1414 , manageHistory
15+ , manageHistory'
1516 , HistoryCommand (..)
1617 , HistoryStateUpdate (..)
1718 , HistoryItem (..)
@@ -23,8 +24,11 @@ import Reflex.Dom.Builder.Immediate (wrapDomEvent)
2324
2425import Control.Lens ((^.))
2526import Control.Monad ((>=>))
27+import Control.Monad.Fix (MonadFix)
28+import Data.Align (align)
2629import Data.Monoid
2730import Data.Text (Text)
31+import Data.These (These(..))
2832import qualified GHCJS.DOM as DOM
2933import qualified GHCJS.DOM.EventM as DOM
3034import qualified GHCJS.DOM.Location as Location
@@ -159,3 +163,38 @@ manageHistory runCmd = do
159163 <*> getLocationUri location
160164 holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
161165--TODO: Handle title setting better
166+
167+manageHistory'
168+ :: (MonadFix m, MonadJSM m, TriggerEvent t m, MonadHold t m, PerformEvent t m, MonadJSM (Performable m))
169+ => Event t ()
170+ -- ^ Don't do anything until this event has fired
171+ -> Event t HistoryCommand
172+ -> m (Dynamic t HistoryItem)
173+manageHistory' switchover runCmd = do
174+ window <- DOM.currentWindowUnchecked
175+ location <- Window.getLocation window
176+ history <- Window.getHistory window
177+ let getCurrentHistoryItem = HistoryItem
178+ <$> History.getState history
179+ <*> getLocationUri location
180+ item0 <- liftJSM getCurrentHistoryItem
181+ itemSetExternal' <- wrapDomEvent window (`DOM.on` DOM.popState) $ do
182+ e <- DOM.event
183+ HistoryItem
184+ <$> (SerializedScriptValue <$> PopStateEvent.getState e)
185+ <*> getLocationUri location
186+ let f :: (Bool, Maybe a) -> These a () -> (Maybe (Bool, Maybe a), Maybe a)
187+ f (switched, acc) = \case
188+ This change
189+ | switched -> (Nothing, Just change)
190+ | otherwise -> (Just (switched, Just change), Nothing)
191+ That () -> (Just (True, Nothing), acc)
192+ These change () -> (Just (True, Nothing), Just change)
193+ -- Accumulate the events before switchover
194+ (_, cmd') <- mapAccumMaybeB f (False, Nothing) $ align (leftmost [Left <$> runCmd, Right <$> itemSetExternal']) switchover
195+ let (itemSetInternal', itemSetExternal) = fanEither cmd'
196+ itemSetInternal <- performEvent $ ffor itemSetInternal' $ \cmd -> liftJSM $ do
197+ runHistoryCommand history cmd
198+ getCurrentHistoryItem
199+ holdDyn item0 $ leftmost [itemSetInternal, itemSetExternal]
200+--TODO: Handle title setting better
reflex-dom-core/src/Reflex/Dom/Main.hs+126−17
@@ -17,6 +17,8 @@ module Reflex.Dom.Main where
1717
1818import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
1919
20+import Reflex.Adjustable.Class
21+import Reflex.Class
2022import Reflex.Dom.Builder.Immediate
2123import Reflex.Dom.Class
2224import Reflex.Host.Class
@@ -24,8 +26,7 @@ import Reflex.PerformEvent.Base
2426import Reflex.PostBuild.Base
2527import Reflex.Spider (Global, Spider, SpiderHost, runSpiderHost)
2628import Reflex.TriggerEvent.Base
27−
28−import Reflex.Dom.Specializations () -- For SPECIALIZATION pragmas, which are always re-exported
29+import Reflex.TriggerEvent.Class
2930
3031import Control.Concurrent
3132import Control.Lens
@@ -34,6 +35,7 @@ import Control.Monad.Reader hiding (forM, forM_, mapM, mapM_, sequence, sequence
3435import Control.Monad.Ref
3536import Data.ByteString (ByteString)
3637import Data.Dependent.Sum (DSum (..))
38+import Data.Foldable (for_)
3739import Data.IORef
3840import Data.Maybe
3941import Data.Monoid ((<>))
@@ -52,6 +54,104 @@ import qualified GHCJS.DOM.Types as DOM
5254import Reflex.Profiled
5355#endif
5456
57+{-# INLINE mainHydrationWidgetWithHead #-}
58+mainHydrationWidgetWithHead :: (forall x. HydrationWidget x ()) -> (forall x. HydrationWidget x ()) -> JSM ()
59+mainHydrationWidgetWithHead = mainHydrationWidgetWithHead'
60+
61+{-# INLINABLE mainHydrationWidgetWithHead' #-}
62+-- | Warning: `mainHydrationWidgetWithHead'` is provided only as performance tweak. It is expected to disappear in future releases.
63+mainHydrationWidgetWithHead' :: HydrationWidget () () -> HydrationWidget () () -> JSM ()
64+mainHydrationWidgetWithHead' = mainHydrationWidgetWithSwitchoverAction' (pure ())
65+
66+{-# INLINE mainHydrationWidgetWithSwitchoverAction #-}
67+mainHydrationWidgetWithSwitchoverAction :: JSM () -> (forall x. HydrationWidget x ()) -> (forall x. HydrationWidget x ()) -> JSM ()
68+mainHydrationWidgetWithSwitchoverAction = mainHydrationWidgetWithSwitchoverAction'
69+
70+{-# INLINABLE mainHydrationWidgetWithSwitchoverAction' #-}
71+-- | Warning: `mainHydrationWidgetWithSwitchoverAction'` is provided only as performance tweak. It is expected to disappear in future releases.
72+mainHydrationWidgetWithSwitchoverAction' :: JSM () -> HydrationWidget () () -> HydrationWidget () () -> JSM ()
73+mainHydrationWidgetWithSwitchoverAction' switchoverAction head' body = do
74+ runHydrationWidgetWithHeadAndBody switchoverAction $ \appendHead appendBody -> do
75+ appendHead head'
76+ appendBody body
77+
78+{-# INLINABLE attachHydrationWidget #-}
79+attachHydrationWidget
80+ :: JSM ()
81+ -> JSContextSingleton ()
82+ -> ( Event DomTimeline ()
83+ -> IORef HydrationMode
84+ -> Maybe (IORef [(Node, HydrationRunnerT DomTimeline (DomCoreWidget ()) ())])
85+ -> EventChannel
86+ -> PerformEventT DomTimeline DomHost (a, IORef (Maybe (EventTrigger DomTimeline ())))
87+ )
88+ -> IO (a, FireCommand DomTimeline DomHost)
89+attachHydrationWidget switchoverAction jsSing w = do
90+ hydrationMode <- liftIO $ newIORef HydrationMode_Hydrating
91+ rootNodesRef <- liftIO $ newIORef []
92+ events <- newChan
93+ runDomHost $ flip runTriggerEventT events $ mdo
94+ (syncEvent, fireSync) <- newTriggerEvent
95+ ((result, postBuildTriggerRef), fc@(FireCommand fire)) <- lift $ hostPerformEventT $ do
96+ a <- w syncEvent hydrationMode (Just rootNodesRef) events
97+ _ <- runWithReplace (return ()) $ delayedAction <$ syncEvent
98+ pure a
99+ mPostBuildTrigger <- readRef postBuildTriggerRef
100+ lift $ forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
101+ liftIO $ fireSync ()
102+ rootNodes <- liftIO $ readIORef rootNodesRef
103+ let delayedAction = do
104+ for_ (reverse rootNodes) $ \(rootNode, runner) -> do
105+ let hydrate = runHydrationRunnerT runner Nothing rootNode events
106+ void $ runWithJSContextSingleton (runPostBuildT hydrate never) jsSing
107+ liftIO $ writeIORef hydrationMode HydrationMode_Immediate
108+ runWithJSContextSingleton (DOM.liftJSM switchoverAction) jsSing
109+ pure (result, fc)
110+
111+type HydrationWidget x a = HydrationDomBuilderT HydrationDomSpace DomTimeline (DomCoreWidget x) a
112+
113+-- | A widget that isn't attached to any particular part of the DOM hierarchy
114+type FloatingWidget x = TriggerEventT DomTimeline (DomCoreWidget x)
115+
116+type DomCoreWidget x = PostBuildT DomTimeline (WithJSContextSingleton x (PerformEventT DomTimeline DomHost))
117+
118+{-# INLINABLE runHydrationWidgetWithHeadAndBody #-}
119+runHydrationWidgetWithHeadAndBody
120+ :: JSM ()
121+ -> ( (forall c. HydrationWidget () c -> FloatingWidget () c) -- "Append to head"
122+ -> (forall c. HydrationWidget () c -> FloatingWidget () c) -- "Append to body"
123+ -> FloatingWidget () ()
124+ )
125+ -> JSM ()
126+runHydrationWidgetWithHeadAndBody switchoverAction app = withJSContextSingletonMono $ \jsSing -> do
127+ globalDoc <- currentDocumentUnchecked
128+ headElement <- getHeadUnchecked globalDoc
129+ bodyElement <- getBodyUnchecked globalDoc
130+ (events, fc) <- liftIO . attachHydrationWidget switchoverAction jsSing $ \switchover hydrationMode hydrationResult events -> do
131+ (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
132+ let hydrateDom :: DOM.Node -> HydrationWidget () c -> FloatingWidget () c
133+ hydrateDom n w = do
134+ delayed <- liftIO $ newIORef $ pure ()
135+ unreadyChildren <- liftIO $ newIORef 0
136+ lift $ do
137+ let builderEnv = HydrationDomBuilderEnv
138+ { _hydrationDomBuilderEnv_document = globalDoc
139+ , _hydrationDomBuilderEnv_parent = Left $ toNode n
140+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
141+ , _hydrationDomBuilderEnv_commitAction = pure ()
142+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
143+ , _hydrationDomBuilderEnv_switchover = switchover
144+ , _hydrationDomBuilderEnv_delayed = delayed
145+ }
146+ a <- runHydrationDomBuilderT w builderEnv events
147+ forM_ hydrationResult $ \hr -> do
148+ res <- liftIO $ readIORef delayed
149+ liftIO $ modifyIORef' hr ((n, res) :)
150+ pure a
151+ runWithJSContextSingleton (runPostBuildT (runTriggerEventT (app (hydrateDom $ toNode headElement) (hydrateDom $ toNode bodyElement)) events) postBuild) jsSing
152+ return (events, postBuildTriggerRef)
153+ liftIO $ processAsyncEvents events fc
154+
55155{-# INLINE mainWidget #-}
56156mainWidget :: (forall x. Widget x ()) -> JSM ()
57157mainWidget = mainWidget'
@@ -103,7 +203,7 @@ runDomHost = runSpiderHost
103203 . runProfiledM
104204#endif
105205
106−type Widget x = PostBuildT DomTimeline (ImmediateDomBuilderT DomTimeline (WithJSContextSingleton x (PerformEventT DomTimeline DomHost))) --TODO: Make this more abstract --TODO: Put the WithJSContext underneath PerformEventT - I think this would perform better because it could avoid fmapping over every performEvent
206+type Widget x = ImmediateDomBuilderT DomTimeline (DomCoreWidget x)
107207
108208{-# INLINABLE attachWidget #-}
109209attachWidget :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> JSM a
@@ -117,19 +217,24 @@ mainWidgetWithHead' widgets = withJSContextSingletonMono $ \jsSing -> do
117217 headFragment <- createDocumentFragment doc
118218 bodyElement <- getBodyUnchecked doc
119219 bodyFragment <- createDocumentFragment doc
220+ hydrationMode <- liftIO $ newIORef HydrationMode_Immediate
120221 (events, fc) <- liftIO . attachWidget'' $ \events -> do
121222 let (headWidget, bodyWidget) = widgets
122223 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
123224 let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT DomTimeline DomHost c
124225 go w df = do
125226 unreadyChildren <- liftIO $ newIORef 0
126− let builderEnv = ImmediateDomBuilderEnv
127− { _immediateDomBuilderEnv_document = toDocument doc
128− , _immediateDomBuilderEnv_parent = toNode df
129− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
130− , _immediateDomBuilderEnv_commitAction = return () --TODO
227+ delayed <- liftIO $ newIORef $ pure ()
228+ let builderEnv = HydrationDomBuilderEnv
229+ { _hydrationDomBuilderEnv_document = toDocument doc
230+ , _hydrationDomBuilderEnv_parent = Left $ toNode df
231+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
232+ , _hydrationDomBuilderEnv_commitAction = return () --TODO
233+ , _hydrationDomBuilderEnv_switchover = never
234+ , _hydrationDomBuilderEnv_delayed = delayed
235+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
131236 }
132− runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
237+ runWithJSContextSingleton (runPostBuildT (runHydrationDomBuilderT w builderEnv events) postBuild) jsSing
133238 rec b <- go (headWidget a) headFragment
134239 a <- go (bodyWidget b) bodyFragment
135240 return (events, postBuildTriggerRef)
@@ -148,16 +253,21 @@ attachWidget' :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> J
148253attachWidget' rootElement jsSing w = do
149254 doc <- getOwnerDocumentUnchecked rootElement
150255 df <- createDocumentFragment doc
256+ hydrationMode <- liftIO $ newIORef HydrationMode_Immediate
151257 ((a, events), fc) <- liftIO . attachWidget'' $ \events -> do
152258 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
153259 unreadyChildren <- liftIO $ newIORef 0
154− let builderEnv = ImmediateDomBuilderEnv
155− { _immediateDomBuilderEnv_document = toDocument doc
156− , _immediateDomBuilderEnv_parent = toNode df
157− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
158− , _immediateDomBuilderEnv_commitAction = return () --TODO
260+ delayed <- liftIO $ newIORef $ pure ()
261+ let builderEnv = HydrationDomBuilderEnv
262+ { _hydrationDomBuilderEnv_document = toDocument doc
263+ , _hydrationDomBuilderEnv_parent = Left $ toNode df
264+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
265+ , _hydrationDomBuilderEnv_commitAction = return () --TODO
266+ , _hydrationDomBuilderEnv_switchover = never
267+ , _hydrationDomBuilderEnv_delayed = delayed
268+ , _hydrationDomBuilderEnv_hydrationMode = hydrationMode
159269 }
160− a <- runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
270+ a <- runWithJSContextSingleton (runPostBuildT (runHydrationDomBuilderT w builderEnv events) postBuild) jsSing
161271 return ((a, events), postBuildTriggerRef)
162272 replaceElementContents rootElement df
163273 liftIO $ processAsyncEvents events fc
@@ -186,7 +296,6 @@ processAsyncEvents events (FireCommand fire) = void $ forkIO $ forever $ do
186296 liftIO $ forM_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb
187297 return ()
188298
189−
190299-- | Run a reflex-dom application inside of an existing DOM element with the given ID
191300mainWidgetInElementById :: Text -> (forall x. Widget x ()) -> JSM ()
192301mainWidgetInElementById eid w = withJSContextSingleton $ \jsSing -> do
@@ -208,7 +317,7 @@ runApp' app = withJSContextSingleton $ \jsSing -> do
208317 body <- getBodyUnchecked doc
209318 win <- getDefaultViewUnchecked doc
210319 rec o <- attachWidget body jsSing $ do
211− w <- lift $ wrapWindow win $ _appOutput_windowConfig o
320+ w <- wrapWindow win $ _appOutput_windowConfig o
212321 app $ AppInput
213322 { _appInput_window = w
214323 }
reflex-dom-core/src/Reflex/Dom/Prerender.hs+183−50
@@ -3,86 +3,219 @@
33{-# LANGUAGE FlexibleContexts #-}
44{-# LANGUAGE FlexibleInstances #-}
55{-# LANGUAGE FunctionalDependencies #-}
6+{-# LANGUAGE LambdaCase #-}
7+{-# LANGUAGE OverloadedStrings #-}
68{-# LANGUAGE MultiParamTypeClasses #-}
79{-# LANGUAGE Rank2Types #-}
10+{-# LANGUAGE RecursiveDo #-}
811{-# LANGUAGE ScopedTypeVariables #-}
912{-# LANGUAGE TypeFamilies #-}
1013{-# LANGUAGE TypeSynonymInstances #-}
1114{-# LANGUAGE UndecidableInstances #-}
15+
16+-- | Render the first widget on the server, and the second on the client.
1217module Reflex.Dom.Prerender
1318 ( Prerender (..)
14− , prerender
19+ , prerender_
1520 , PrerenderClientConstraint
21+ , PrerenderBaseConstraints
1622 ) where
1723
24+import Control.Monad.Primitive (PrimMonad)
1825import Control.Monad.Reader
19−import Data.Constraint
26+import Control.Monad.Ref (MonadRef(..))
27+import Data.IORef (IORef, newIORef)
28+import Data.Text (Text)
2029import Foreign.JavaScript.TH
2130import GHCJS.DOM.Types (MonadJSM)
22−import Reflex
31+import Reflex hiding (askEvents)
2332import Reflex.Dom.Builder.Class
24−import Reflex.Dom.Builder.InputDisabled
2533import Reflex.Dom.Builder.Immediate
34+import Reflex.Dom.Builder.InputDisabled
2635import Reflex.Dom.Builder.Static
2736import Reflex.Host.Class
37+import Reflex.Requester.Base
38+import Data.IntMap.Strict (IntMap)
39+import qualified Data.IntMap.Strict as IntMap
2840
29−type PrerenderClientConstraint js m =
30− ( HasJS js m
31− , HasJS js (Performable m)
32− , MonadJSM m
33− , MonadJSM (Performable m)
41+import qualified GHCJS.DOM.Document as Document
42+import qualified GHCJS.DOM.Node as Node
43+import qualified GHCJS.DOM.Types as DOM
44+
45+type PrerenderClientConstraint js t m =
46+ ( DomBuilder t m
47+ , DomBuilderSpace m ~ GhcjsDomSpace
48+ , DomRenderHook t m
49+ , HasDocument m
50+ , TriggerEvent t m
51+ , Prerender js t m
52+ , PrerenderBaseConstraints js t m
53+ )
54+
55+type PrerenderBaseConstraints js t m =
56+ ( HasJSContext (Performable m)
3457 , HasJSContext m
35− , HasJSContext (Performable m)
3658 , MonadFix m
37− , MonadFix (Performable m)
38− , DomBuilderSpace m ~ GhcjsDomSpace
59+ , MonadHold t m
60+ , MonadJSM (Performable m)
61+ , MonadJSM m
62+ , MonadRef (Performable m)
63+ , MonadRef m
64+ , MonadReflexCreateTrigger t m
65+ , MonadSample t (Performable m)
66+ , PerformEvent t m
67+ , PostBuild t m
68+ , PrimMonad m
69+ , Ref (Performable m) ~ IORef
70+ , Ref m ~ IORef
71+ , HasJS js m
72+ , HasJS js (Performable m)
3973 )
4074
41−class Prerender js m | m -> js where
42− prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m))
43−
44−-- | Draw one widget when prerendering (e.g. server-side) and another when the
45−-- widget is fully instantiated. In a given execution of this function, there
46−-- will be exactly one invocation of exactly one of the arguments.
47−prerender :: forall js m a. Prerender js m => m a -> (PrerenderClientConstraint js m => m a) -> m a
48−prerender server client = case prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)) of
49− Nothing -> server
50− Just Dict -> client
51−
52−instance ( HasJS js m
53− , HasJS js (Performable m)
54− , HasJSContext m
55− , HasJSContext (Performable m)
56− , MonadJSM m
57− , MonadJSM (Performable m)
58− , MonadFix m
59− , MonadFix (Performable m)
60− , ReflexHost t
61− ) => Prerender js (ImmediateDomBuilderT t m) where
62− prerenderClientDict = Just Dict
75+-- | Render the first widget on the server, and the second on the client. The
76+-- hydration builder will run *both* widgets.
77+prerender_
78+ :: (Functor m, Reflex t, Prerender js t m)
79+ => m () -> ((PrerenderClientConstraint js t (Client m)) => Client m ()) -> m ()
80+prerender_ server client = void $ prerender server client
81+
82+class Prerender js t m | m -> t js where
83+ -- | Monad in which the client widget is built
84+ type Client m :: * -> *
85+ -- | Render the first widget on the server, and the second on the client. The
86+ -- hydration builder will run *both* widgets, updating the result dynamic at
87+ -- switchover time.
88+ prerender :: m a -> ((PrerenderClientConstraint js t (Client m)) => Client m a) -> m (Dynamic t a)
89+
90+instance (ReflexHost t, Adjustable t m, PrerenderBaseConstraints js t m) => Prerender js t (HydrationDomBuilderT GhcjsDomSpace t m) where
91+ type Client (HydrationDomBuilderT GhcjsDomSpace t m) = HydrationDomBuilderT GhcjsDomSpace t m
92+ prerender _ client = pure <$> client
93+
94+instance (Adjustable t m, PrerenderBaseConstraints js t m, ReflexHost t) => Prerender js t (HydrationDomBuilderT HydrationDomSpace t m) where
95+ -- | PostBuildT is needed here because we delay running the client builder
96+ -- until after switchover, at which point the postBuild of @m@ has already fired
97+ type Client (HydrationDomBuilderT HydrationDomSpace t m) = PostBuildT t (HydrationDomBuilderT GhcjsDomSpace t m)
98+ -- | Runs the server widget up until switchover, then replaces it with the
99+ -- client widget.
100+ prerender server client = do
101+ env <- HydrationDomBuilderT ask
102+ events <- askEvents
103+ doc <- askDocument
104+ serverDf <- Document.createDocumentFragment doc -- server dom should not be mounted in the window's doc in hydration
105+ df <- Document.createDocumentFragment doc
106+ unreadyChildren <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_unreadyChildren
107+ immediateMode <- liftIO $ newIORef HydrationMode_Immediate
108+ delayed <- liftIO $ newIORef $ pure ()
109+ let clientEnv = env
110+ { _hydrationDomBuilderEnv_parent = Left $ DOM.toNode df
111+ , _hydrationDomBuilderEnv_hydrationMode = immediateMode
112+ }
113+ serverEnv = HydrationDomBuilderEnv
114+ { _hydrationDomBuilderEnv_document = doc
115+ , _hydrationDomBuilderEnv_parent = Left $ DOM.toNode serverDf
116+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
117+ , _hydrationDomBuilderEnv_commitAction = pure ()
118+ , _hydrationDomBuilderEnv_delayed = delayed
119+ , _hydrationDomBuilderEnv_hydrationMode = immediateMode
120+ , _hydrationDomBuilderEnv_switchover = never
121+ }
122+ a0 <- lift $ runHydrationDomBuilderT server serverEnv events
123+ (a', trigger) <- newTriggerEvent
124+ getHydrationMode >>= \case
125+ HydrationMode_Immediate -> do
126+ liftIO . trigger <=< lift $ runHydrationDomBuilderT (runPostBuildT client $ void a') clientEnv events
127+ append $ DOM.toNode df
128+ HydrationMode_Hydrating -> addHydrationStep $ do
129+ liftIO . trigger <=< lift $ runHydrationDomBuilderT (runPostBuildT client $ void a') clientEnv events
130+ insertBefore df =<< deleteToPrerenderEnd doc
131+ holdDyn a0 a'
63132
64133data NoJavaScript -- This type should never have a HasJS instance
65134
66−instance js ~ NoJavaScript => Prerender js (StaticDomBuilderT t m) where
67− prerenderClientDict = Nothing
135+instance (js ~ NoJavaScript, SupportsStaticDomBuilder t m) => Prerender js t (StaticDomBuilderT t m) where
136+ type Client (StaticDomBuilderT t m) = HydrationDomBuilderT GhcjsDomSpace t m
137+ prerender server _ = do
138+ _ <- commentNode $ CommentNodeConfig startMarker Nothing
139+ a <- server
140+ _ <- commentNode $ CommentNodeConfig endMarker Nothing
141+ pure $ pure a
142+
143+instance (Prerender js t m, Monad m) => Prerender js t (ReaderT r m) where
144+ type Client (ReaderT r m) = ReaderT r (Client m)
145+ prerender server client = do
146+ r <- ask
147+ lift $ prerender (runReaderT server r) (runReaderT client r)
148+
149+instance (Prerender js t m, Monad m, Reflex t, MonadFix m, Monoid w) => Prerender js t (DynamicWriterT t w m) where
150+ type Client (DynamicWriterT t w m) = DynamicWriterT t w (Client m)
151+ prerender server client = do
152+ x <- lift $ prerender (runDynamicWriterT server) (runDynamicWriterT client)
153+ let (a, w') = splitDynPure x
154+ w = join w'
155+ tellDyn w
156+ pure a
68157
69−instance (Prerender js m, ReflexHost t) => Prerender js (PostBuildT t m) where
70− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
158+instance (Prerender js t m, Monad m, Reflex t, Semigroup w) => Prerender js t (EventWriterT t w m) where
159+ type Client (EventWriterT t w m) = EventWriterT t w (Client m)
160+ prerender server client = do
161+ x <- lift $ prerender (runEventWriterT server) (runEventWriterT client)
162+ let (a, w') = splitDynPure x
163+ w = switch $ current w'
164+ tellEvent w
165+ pure a
71166
72−instance Prerender js m => Prerender js (DynamicWriterT t w m) where
73− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
167+instance (Prerender js t m, MonadFix m, Reflex t) => Prerender js t (RequesterT t request response m) where
168+ type Client (RequesterT t request response m) = RequesterT t request response (Client m)
169+ prerender server client = mdo
170+ let fannedResponses = fanInt responses
171+ withFannedResponses :: forall m' a. Monad m' => RequesterT t request response m' a -> Int -> m' (a, Event t (IntMap (RequesterData request)))
172+ withFannedResponses w selector = do
173+ (x, e) <- runRequesterT w (selectInt fannedResponses selector)
174+ pure (x, fmapCheap (IntMap.singleton selector) e)
175+ (result, requestsDyn) <- fmap splitDynPure $ lift $ prerender (withFannedResponses server 0) (withFannedResponses client 1)
176+ responses <- fmap (fmapCheap unMultiEntry) $ requesting' $ fmapCheap multiEntry $ switch $ current requestsDyn
177+ pure result
74178
75−instance Prerender js m => Prerender js (EventWriterT t w m) where
76− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
179+instance (Prerender js t m, Monad m, Reflex t, MonadFix m, Group q, Additive q, Query q) => Prerender js t (QueryT t q m) where
180+ type Client (QueryT t q m) = QueryT t q (Client m)
181+ prerender server client = mdo
182+ result <- queryDyn query
183+ x <- lift $ prerender (runQueryT server result) (runQueryT client result)
184+ let (a, inc) = splitDynPure x
185+ query = incrementalToDynamic =<< inc -- Can we avoid the incrementalToDynamic?
186+ pure a
77187
78−instance Prerender js m => Prerender js (ReaderT w m) where
79− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
188+instance (Prerender js t m, Monad m) => Prerender js t (InputDisabledT m) where
189+ type Client (InputDisabledT m) = Client m
190+ prerender (InputDisabledT server) client = InputDisabledT $ prerender server client
80191
81−instance Prerender js m => Prerender js (RequesterT t request response m) where
82− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
192+instance (Prerender js t m, Monad m, ReflexHost t) => Prerender js t (PostBuildT t m) where
193+ type Client (PostBuildT t m) = PostBuildT t (Client m)
194+ prerender server client = PostBuildT $ do
195+ pb <- ask
196+ lift $ prerender (runPostBuildT server pb) (runPostBuildT client pb)
83197
84−instance Prerender js m => Prerender js (QueryT t q m) where
85− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
198+startMarker, endMarker :: Text
199+startMarker = "prerender/start"
200+endMarker = "prerender/end"
86201
87−instance Prerender js m => Prerender js (InputDisabledT m) where
88− prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
202+deleteToPrerenderEnd :: (MonadIO m, MonadJSM m, Reflex t, MonadFix m) => DOM.Document -> HydrationRunnerT t m DOM.Comment
203+deleteToPrerenderEnd doc = do
204+ startNode <- hydrateComment doc startMarker Nothing
205+ let go (n :: Int) lastNode = Node.getNextSibling lastNode >>= \case
206+ Nothing -> do
207+ c <- Document.createComment doc endMarker
208+ insertAfterPreviousNode c
209+ pure c
210+ Just node -> DOM.castTo DOM.Comment node >>= \case
211+ Nothing -> go n node
212+ Just c -> Node.getTextContentUnchecked c >>= \case
213+ t | t == startMarker -> go (succ n) node
214+ | t == endMarker -> case n of
215+ 0 -> pure c
216+ _ -> go (pred n) node
217+ | otherwise -> go n node
218+ endNode <- go 0 $ DOM.toNode startNode
219+ deleteBetweenExclusive startNode endNode
220+ setPreviousNode $ Just $ DOM.toNode endNode
221+ pure endNode
reflex-dom-core/src/Reflex/Dom/Specializations.hsdeleted−62
@@ -1,62 +0,0 @@
1−{-# OPTIONS_HADDOCK hide #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE GADTs #-}
4−{-# LANGUAGE Rank2Types #-}
5−{-# LANGUAGE ScopedTypeVariables #-}
6−module Reflex.Dom.Specializations where
7−
8−import Data.Text (Text)
9−import Reflex.Spider
10−import Reflex.Class
11−import Foreign.JavaScript.TH
12−import Reflex.PerformEvent.Base
13−import Reflex.PostBuild.Base
14−import Reflex.Requester.Base
15−import Reflex.TriggerEvent.Base
16−import Reflex.Dom.Builder.Class
17−import Reflex.Dom.Builder.Immediate
18−import Data.Functor.Identity
19−import Data.Functor.Compose
20−import Data.IORef
21−import GHCJS.DOM.Types (JSM)
22−import Data.IntMap.Strict (IntMap)
23−import qualified Data.IntMap.Strict as IntMap
24−
25−import qualified GHCJS.DOM.Element as DOM
26−import qualified GHCJS.DOM.Types as DOM
27−
28−{-# SPECIALIZE makeElement :: forall er a. Text -> ElementConfig er (SpiderTimeline Global) GhcjsDomSpace -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) a -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) ((Element er GhcjsDomSpace (SpiderTimeline Global), a), DOM.Element) #-}
29−
30−{-# SPECIALIZE wrap :: forall er. RawElement GhcjsDomSpace -> RawElementConfig er (SpiderTimeline Global) GhcjsDomSpace -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (Element er GhcjsDomSpace (SpiderTimeline Global)) #-}
31−
32−{-# SPECIALIZE mapIntMapWithAdjustImpl :: forall v v'.
33− ( (IntMap.Key -> (Event (SpiderTimeline Global) (), v) -> (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) v')
34− -> IntMap (Event (SpiderTimeline Global) (), v)
35− -> Event (SpiderTimeline Global) (PatchIntMap (Event (SpiderTimeline Global) (), v))
36− -> (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v'))
37− )
38− -> (IntMap.Key -> v -> PostBuildT (SpiderTimeline Global) (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) v')
39− -> IntMap v
40− -> Event (SpiderTimeline Global) (PatchIntMap v)
41− -> PostBuildT (SpiderTimeline Global) (ImmediateDomBuilderT Spider (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v'))
42− #-}
43−
44−{-# SPECIALIZE append :: DOM.Node -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) () #-}
45−
46−{-# SPECIALIZE drawChildUpdate :: forall k v' a. ImmediateDomBuilderEnv (SpiderTimeline Global) -> (IORef (ChildReadyState k) -> JSM ()) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (v' a) -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a) #-}
47−
48−{-# SPECIALIZE traverseIntMapWithKeyWithAdjust' :: forall v v'. (IntMap.Key -> v -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) v') -> IntMap v -> Event (SpiderTimeline Global) (PatchIntMap v) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v')) #-}
49−
50−{-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust :: forall v v'.
51− ( (IntMap.Key -> v -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
52− -> IntMap v
53− -> Event (SpiderTimeline Global) (PatchIntMap v)
54− -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event (SpiderTimeline Global) (PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')))
55− )
56− -> (PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt)))
57− -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ())
58− -> (IntMap.Key -> v -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) v')
59− -> IntMap v
60− -> Event (SpiderTimeline Global) (PatchIntMap v)
61− -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v'))
62− #-}
reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+15
@@ -14,6 +14,8 @@ module Reflex.Dom.Widget.Basic
1414 -- * Displaying Values
1515 text
1616 , dynText
17+ , comment
18+ , dynComment
1719 , display
1820 , button
1921 , dyn
@@ -119,6 +121,19 @@ dynText t = do
119121 ]
120122 notReadyUntil postBuild
121123
124+comment :: DomBuilder t m => Text -> m ()
125+comment t = void $ commentNode $ def & commentNodeConfig_initialContents .~ t
126+
127+{-# INLINABLE dynComment #-}
128+dynComment :: forall t m. (PostBuild t m, DomBuilder t m) => Dynamic t Text -> m ()
129+dynComment t = do
130+ postBuild <- getPostBuild
131+ void $ commentNode $ (def :: CommentNodeConfig t) & commentNodeConfig_setContents .~ leftmost
132+ [ updated t
133+ , tag (current t) postBuild
134+ ]
135+ notReadyUntil postBuild
136+
122137display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
123138display = dynText . fmap (T.pack . show)
124139
reflex-dom-core/test/gc.hs+12−11
@@ -8,16 +8,17 @@ import Control.Exception
88import Control.Monad
99import Control.Monad.IO.Class
1010import Data.Int
11+import Data.Text as T
1112import Language.Javascript.JSaddle.Warp
1213import Reflex.Dom.Core
1314import Reflex.Time
1415import System.Exit
15−import System.IO.Temp
16−import System.Linux.Namespaces
1716import System.Mem
18−import System.Posix
1917import System.Process
2018
19+import Test.Util.ChromeFlags
20+import Test.Util.UnshareNetwork
21+
2122#if MIN_VERSION_base(4,11,0)
2223import GHC.Stats (getRTSStatsEnabled, getRTSStats, RTSStats(..), gcdetails_live_bytes, gc)
2324currentBytesUsed :: RTSStats -> Int64
@@ -46,14 +47,14 @@ failureLimit = 0
4647
4748main :: IO ()
4849main = do
49− uid <- getEffectiveUserID
50− handle (\(_ :: IOError) -> return ()) $ do -- If we run into an exception with sandboxing, just don't bother
51− unshare [User, Network]
52− writeUserMappings Nothing [UserMapping 0 uid 1]
53− callCommand "ip link set lo up ; ip addr"
54− mainThread <- myThreadId
55− withSystemTempDirectory "reflex-dom-core_test_gc" $ \tmp -> do
56− browserProcess <- spawnCommand $ "echo 'Starting Chromium' ; chromium --headless --disable-gpu --no-sandbox --remote-debugging-port=9222 --user-data-dir=" ++ tmp ++ " http://localhost:3911 ; echo 'Chromium exited'"
50+ handle (\(_ :: IOError) -> return ()) $ unshareNetork -- If we run into an exception with sandboxing, just don't bother
51+ withSandboxedChromeFlags True $ \chromeFlags -> do
52+ mainThread <- myThreadId
53+ browserProcess <- spawnCommand $ mconcat
54+ [ "echo 'Starting Chromium' ; chromium "
55+ , unpack $ T.unwords chromeFlags
56+ , " http://localhost:3911 ; echo 'Chromium exited'"
57+ ]
5758 let finishTest result = do
5859 interruptProcessGroupOf browserProcess
5960 throwTo mainThread result
reflex-dom-core/test/hydration.hsnew file+1423
@@ -0,0 +1,1423 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE StandaloneDeriving #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE UndecidableInstances #-}
6+{-# LANGUAGE ConstraintKinds #-}
7+{-# LANGUAGE CPP #-}
8+{-# LANGUAGE FlexibleContexts #-}
9+{-# LANGUAGE GADTs #-}
10+{-# LANGUAGE LambdaCase #-}
11+{-# LANGUAGE MultiParamTypeClasses #-}
12+{-# LANGUAGE OverloadedStrings #-}
13+{-# LANGUAGE PolyKinds #-}
14+{-# LANGUAGE RankNTypes #-}
15+{-# LANGUAGE RecursiveDo #-}
16+{-# LANGUAGE ScopedTypeVariables #-}
17+{-# LANGUAGE TemplateHaskell #-}
18+{-# LANGUAGE TupleSections #-}
19+{-# LANGUAGE TypeApplications #-}
20+{-# LANGUAGE TypeFamilies #-}
21+
22+import Control.Concurrent
23+import Control.Monad
24+import Control.Monad.Catch
25+import Control.Monad.Fix
26+import Control.Monad.IO.Class
27+import Control.Monad.Ref
28+import Data.Constraint.Extras
29+import Data.Constraint.Extras.TH
30+import Data.Dependent.Map (DMap)
31+import Data.Dependent.Sum (DSum(..), (==>), EqTag(..), ShowTag(..))
32+import Data.Functor.Identity
33+import Data.Functor.Misc
34+import Data.GADT.Compare.TH
35+import Data.GADT.Show.TH
36+import Data.IORef (IORef)
37+import Data.Maybe
38+import Data.Proxy
39+import Data.Text (Text)
40+import Language.Javascript.JSaddle (syncPoint, liftJSM)
41+import Language.Javascript.JSaddle.Warp
42+import Network.HTTP.Types (status200)
43+import Network.Socket
44+import Network.Wai
45+import Network.WebSockets
46+import Reflex.Dom.Core
47+import Reflex.Patch.DMapWithMove
48+import System.Directory
49+import System.Environment
50+import System.IO (stderr)
51+import System.IO.Silently
52+import System.IO.Temp
53+import System.Process
54+import qualified Test.HUnit as HUnit (assertEqual, assertFailure)
55+import qualified Test.Hspec as H
56+import Test.Hspec (xit)
57+import Test.Hspec.WebDriver hiding (runWD, click, uploadFile)
58+import qualified Test.Hspec.WebDriver as WD
59+import Test.WebDriver (WD)
60+
61+import qualified Data.ByteString.Lazy as LBS
62+import qualified Data.Dependent.Map as DMap
63+import qualified Data.IntMap as IntMap
64+import qualified Data.Text as T
65+import qualified Data.Text.Encoding as TE
66+import qualified GHCJS.DOM.File as File
67+import qualified Network.Wai.Handler.Warp as Warp
68+import qualified System.FilePath as FilePath
69+import qualified Test.WebDriver as WD
70+import qualified Test.WebDriver.Capabilities as WD
71+
72+import Test.Util.ChromeFlags
73+import Test.Util.UnshareNetwork
74+
75+seleniumPort, jsaddlePort :: PortNumber
76+seleniumPort = 8000
77+jsaddlePort = 8001
78+
79+-- TODO Remove orphan
80+deriving instance MonadRef WD
81+
82+assertEqual :: (MonadIO m, Eq a, Show a) => String -> a -> a -> m ()
83+assertEqual a b = liftIO . HUnit.assertEqual a b
84+
85+assertFailure :: MonadIO m => String -> m ()
86+assertFailure = liftIO . HUnit.assertFailure
87+
88+chromeConfig :: Text -> [Text] -> WD.WDConfig
89+chromeConfig fp flags = WD.useBrowser (WD.chrome { WD.chromeBinary = Just $ T.unpack fp, WD.chromeOptions = T.unpack <$> flags }) WD.defaultConfig
90+
91+keyMap :: DMap DKey Identity
92+keyMap = DMap.fromList
93+ [ Key_Int ==> 0
94+ , Key_Char ==> 'A'
95+ ]
96+
97+data DKey a where
98+ Key_Int :: DKey Int
99+ Key_Char :: DKey Char
100+ Key_Bool :: DKey Bool
101+
102+
103+textKey :: DKey a -> Text
104+textKey = \case
105+ Key_Int -> "Key_Int"
106+ Key_Char -> "Key_Char"
107+ Key_Bool -> "Key_Bool"
108+
109+deriveArgDict ''DKey
110+deriveGEq ''DKey
111+deriveGCompare ''DKey
112+deriveGShow ''DKey
113+
114+instance ShowTag DKey Identity where
115+ showTaggedPrec = \case
116+ Key_Int -> showsPrec
117+ Key_Char -> showsPrec
118+ Key_Bool -> showsPrec
119+
120+instance EqTag DKey Identity where
121+ eqTagged Key_Int Key_Int = (==)
122+ eqTagged Key_Char Key_Char = (==)
123+ eqTagged Key_Bool Key_Bool = (==)
124+
125+main :: IO ()
126+main = do
127+ unshareNetork
128+ isHeadless <- (== Nothing) <$> lookupEnv "NO_HEADLESS"
129+ withSandboxedChromeFlags isHeadless $ \chromeFlags -> do
130+ withSeleniumServer $ \selenium -> do
131+ browserPath <- liftIO $ T.strip . T.pack <$> readProcess "which" [ "chromium" ] ""
132+ when (T.null browserPath) $ fail "No browser found"
133+ withDebugging <- isJust <$> lookupEnv "DEBUG"
134+ let wdConfig = WD.defaultConfig { WD.wdPort = fromIntegral $ _selenium_portNumber selenium }
135+ chromeCaps' = WD.getCaps $ chromeConfig browserPath chromeFlags
136+ hspec (tests withDebugging wdConfig [chromeCaps'] selenium) `finally` _selenium_stopServer selenium
137+
138+tests :: Bool -> WD.WDConfig -> [Capabilities] -> Selenium -> Spec
139+tests withDebugging wdConfig caps _selenium = do
140+ let putStrLnDebug :: MonadIO m => Text -> m ()
141+ putStrLnDebug m = when withDebugging $ liftIO $ putStrLn $ T.unpack m
142+ session' = sessionWith wdConfig "" . using (map (,"") caps)
143+ runWD m = runWDOptions (WdOptions False) $ do
144+ putStrLnDebug "before"
145+ r <- m
146+ putStrLnDebug "after"
147+ return r
148+ testWidgetStatic :: WD b -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> WD b
149+ testWidgetStatic = testWidgetStaticDebug withDebugging
150+ testWidget :: WD () -> WD b -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> WD b
151+ testWidget = testWidgetDebug withDebugging
152+ testWidget' :: WD a -> (a -> WD b) -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ()) -> WD b
153+ testWidget' = testWidgetDebug' withDebugging
154+ describe "text" $ session' $ do
155+ it "works" $ runWD $ do
156+ testWidgetStatic (checkBodyText "hello world") $ do
157+ text "hello world"
158+ it "works with postBuild" $ runWD $ do
159+ testWidgetStatic (checkBodyText "pb") $ do
160+ pb <- getPostBuild
161+ void $ textNode $ TextNodeConfig "" $ Just $ "pb" <$ pb
162+ it "works for adjacent text nodes" $ runWD $ do
163+ testWidgetStatic (checkBodyText "hello world") $ do
164+ text "hello "
165+ text "world"
166+ it "works for empty adjacent text nodes" $ runWD $ do
167+ testWidgetStatic (checkBodyText "hello world") $ do
168+ pb <- getPostBuild
169+ text ""
170+ text ""
171+ _ <- textNode $ TextNodeConfig "" $ Just $ "hello " <$ pb
172+ _ <- textNode $ TextNodeConfig "abc" $ Just $ "" <$ pb
173+ _ <- textNode $ TextNodeConfig "" $ Just $ "world" <$ pb
174+ text ""
175+ it "works when empty text nodes are the only children of an element" $ runWD $ do
176+ testWidgetStatic (checkBodyText "hello world") $ do
177+ el "div" $ do
178+ text ""
179+ text ""
180+ text "hello world"
181+ it "works when an empty text node is the first child before text" $ runWD $ do
182+ testWidgetStatic (checkTextInTag "div" "hello world") $ do
183+ el "div" $ do
184+ text ""
185+ text "hello world"
186+ it "works when an empty text node is the first child before element" $ runWD $ do
187+ testWidgetStatic (checkTextInTag "div" "hello world") $ do
188+ el "div" $ do
189+ text ""
190+ el "span" $ text "hello world"
191+ it "works when an empty text node is the last child" $ runWD $ do
192+ testWidgetStatic (checkTextInTag "div" "hello world") $ do
193+ el "div" $ do
194+ el "span" $ text "hello world"
195+ text ""
196+-- TODO I think these two tests are broken
197+-- it "updates after postBuild" $ runWD $ do
198+-- testWidget (checkBodyText "initial") (checkBodyText "after") $ do
199+-- after <- delay 0 =<< getPostBuild
200+-- void $ textNode $ TextNodeConfig "initial" $ Just $ "after" <$ after
201+-- it "updates immediately after postBuild" $ runWD $ do
202+-- testWidget (checkBodyText "pb") (checkBodyText "after") $ do
203+-- pb <- getPostBuild
204+-- after <- delay 0 pb
205+-- void $ textNode $ TextNodeConfig "initial" $ Just $ leftmost ["pb" <$ pb, "after" <$ after]
206+ it "updates in immediate mode" $ runWD $ do
207+ let checkUpdated = do
208+ checkBodyText "initial"
209+ WD.click =<< findElemWithRetry (WD.ByTag "button")
210+ checkBodyText "after"
211+ testWidget (pure ()) checkUpdated $ prerender_ (pure ()) $ do
212+ click <- button ""
213+ void $ textNode $ TextNodeConfig "initial" $ Just $ "after" <$ click
214+
215+ describe "element" $ session' $ do
216+ it "works with domEvent Click" $ runWD $ do
217+ clickedRef <- liftIO $ newRef False
218+ testWidget' (findElemWithRetry $ WD.ByTag "div") WD.click $ do
219+ (e, _) <- el' "div" $ text "hello world"
220+ performEvent_ $ liftIO (writeRef clickedRef True) <$ domEvent Click e
221+ readRef clickedRef `shouldBeWithRetryM` True
222+ it "works with eventFlags stopPropagation" $ runWD $ do
223+ firstClickedRef <- newRef False
224+ secondClickedRef <- newRef False
225+ let clickBoth = do
226+ findElemWithRetry (WD.ById "first") >>= WD.click
227+ findElemWithRetry (WD.ById "second") >>= WD.click
228+ testWidget (pure ()) clickBoth $ do
229+ (firstDivEl, _) <- el' "div" $ prerender_ (pure ()) $ do
230+ void $ elAttr "span" ("id" =: "first") $ text "hello world"
231+ performEvent_ $ liftIO (writeRef firstClickedRef True) <$ domEvent Click firstDivEl
232+ (secondDivEl, _) <- el' "div" $ prerender_ (pure ()) $ do
233+ let conf :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace
234+ conf = (def :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace)
235+ & initialAttributes .~ "id" =: "second"
236+ & elementConfig_eventSpec .~ addEventSpecFlags (Proxy :: Proxy GhcjsDomSpace) Click (\_ -> stopPropagation) def
237+ void $ element "span" conf $ text "hello world"
238+ performEvent_ $ liftIO (writeRef secondClickedRef True) <$ domEvent Click secondDivEl
239+ firstClicked <- readRef firstClickedRef
240+ secondClicked <- readRef secondClickedRef
241+ assertEqual "Click propagated when it should have stopped" (True, False) (firstClicked, secondClicked)
242+ it "works with eventFlags preventDefault" $ runWD $ do
243+ let click = do
244+ e <- findElemWithRetry $ WD.ByTag "input"
245+ s0 <- WD.isSelected e
246+ WD.click e
247+ s1 <- WD.isSelected e
248+ pure (s0, s1)
249+ clicked <- testWidget (pure ()) click $ prerender_ (pure ()) $ do
250+ let conf :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace
251+ conf = (def :: ElementConfig EventResult (SpiderTimeline Global) GhcjsDomSpace)
252+ & elementConfig_eventSpec .~ addEventSpecFlags (Proxy :: Proxy GhcjsDomSpace) Click (\_ -> preventDefault) def
253+ & initialAttributes .~ "type" =: "checkbox"
254+ void $ element "input" conf $ text "hello world"
255+ assertEqual "Click not prevented" (False, False) clicked
256+ it "can add/update/remove attributes" $ runWD $ do
257+ let checkInitialAttrs = do
258+ e <- findElemWithRetry $ WD.ByTag "div"
259+ assertAttr e "const" (Just "const")
260+ assertAttr e "delete" (Just "delete")
261+ assertAttr e "init" (Just "init")
262+ assertAttr e "click" Nothing
263+ pure e
264+ checkModifyAttrs e = do
265+ WD.click e
266+ withRetry $ do
267+ assertAttr e "const" (Just "const")
268+ assertAttr e "delete" Nothing
269+ assertAttr e "init" (Just "click")
270+ assertAttr e "click" (Just "click")
271+ testWidget' checkInitialAttrs checkModifyAttrs $ mdo
272+ let conf = def
273+ & initialAttributes .~ "const" =: "const" <> "delete" =: "delete" <> "init" =: "init"
274+ & modifyAttributes .~ (("delete" =: Nothing <> "init" =: Just "click" <> "click" =: Just "click") <$ click)
275+ (e, ()) <- element "div" conf $ text "hello world"
276+ let click = domEvent Click e
277+ return ()
278+ -- TODO check this is the correct solution
279+ it "has ssr attribute, removes ssr attribute" $ runWD $ do
280+ let checkSSRAttr = do
281+ e <- findElemWithRetry $ WD.ByTag "div"
282+ assertAttr e "data-ssr" (Just "")
283+ pure e
284+ testWidget' checkSSRAttr (\e -> assertAttr e "data-ssr" Nothing) $ el "div" $ text "hello world"
285+
286+ describe "inputElement" $ do
287+ describe "hydration" $ session' $ do
288+ it "doesn't wipe user input when switching over" $ runWD $ do
289+ inputRef <- newRef ("" :: Text)
290+ testWidget'
291+ (do
292+ e <- findElemWithRetry $ WD.ByTag "input"
293+ WD.sendKeys "hello world" e
294+ pure e)
295+ (\e -> do
296+ WD.attr e "value" `shouldBeWithRetryM` Just "hello world"
297+ WD.click =<< findElemWithRetry (WD.ByTag "button")
298+ readRef inputRef `shouldBeWithRetryM` "hello world"
299+ ) $ do
300+ e <- inputElement def
301+ click <- button "save"
302+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
303+ it "captures user input after switchover" $ runWD $ do
304+ inputRef <- newRef ("" :: Text)
305+ let checkValue = do
306+ WD.sendKeys "hello world" =<< findElemWithRetry (WD.ByTag "input")
307+ WD.click =<< findElemWithRetry (WD.ByTag "button")
308+ readRef inputRef `shouldBeWithRetryM` "hello world"
309+ testWidget (pure ()) checkValue $ do
310+ e <- inputElement def
311+ click <- button "save"
312+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
313+ it "sets focus appropriately" $ runWD $ do
314+ focusRef <- newRef False
315+ let checkValue = do
316+ readRef focusRef `shouldBeWithRetryM` False
317+ e <- findElemWithRetry $ WD.ByTag "input"
318+ WD.click e
319+ readRef focusRef `shouldBeWithRetryM` True
320+ testWidget (pure ()) checkValue $ do
321+ e <- inputElement def
322+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_inputElement_hasFocus e)
323+ it "sets focus when focus occurs before hydration" $ runWD $ do
324+ focusRef <- newRef False
325+ let setup = do
326+ e <- findElemWithRetry $ WD.ByTag "input"
327+ WD.click e
328+ ((== e) <$> WD.activeElem) `shouldBeWithRetryM` True
329+ readRef focusRef `shouldBeWithRetryM` False
330+ check = readRef focusRef `shouldBeWithRetryM` True
331+ testWidget setup check $ do
332+ e <- inputElement def
333+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_inputElement_hasFocus e)
334+ it "sets value appropriately" $ runWD $ do
335+ valueByUIRef <- newRef ("" :: Text)
336+ valueRef <- newRef ("" :: Text)
337+ setValueChan :: Chan Text <- liftIO newChan
338+ let checkValue = do
339+ readRef valueByUIRef `shouldBeWithRetryM` ""
340+ readRef valueRef `shouldBeWithRetryM` ""
341+ e <- findElemWithRetry $ WD.ByTag "input"
342+ WD.sendKeys "hello" e
343+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
344+ readRef valueRef `shouldBeWithRetryM` "hello"
345+ liftIO $ writeChan setValueChan "world"
346+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
347+ readRef valueRef `shouldBeWithRetryM` "world"
348+ testWidget (pure ()) checkValue $ do
349+ update <- triggerEventWithChan setValueChan
350+ e <- inputElement $ def & inputElementConfig_setValue .~ update
351+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _inputElement_input e
352+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
353+ it "sets checked appropriately" $ runWD $ do
354+ checkedByUIRef <- newRef False
355+ checkedRef <- newRef False
356+ setCheckedChan <- liftIO newChan
357+ let checkValue = do
358+ readRef checkedByUIRef `shouldBeWithRetryM` False
359+ readRef checkedRef `shouldBeWithRetryM` False
360+ e <- findElemWithRetry $ WD.ByTag "input"
361+ WD.moveToCenter e
362+ WD.click e
363+ readRef checkedByUIRef `shouldBeWithRetryM` True
364+ readRef checkedRef `shouldBeWithRetryM` True
365+ liftIO $ writeChan setCheckedChan False
366+ readRef checkedByUIRef `shouldBeWithRetryM` True
367+ readRef checkedRef `shouldBeWithRetryM` False
368+ testWidget (pure ()) checkValue $ do
369+ setChecked <- triggerEventWithChan setCheckedChan
370+ e <- inputElement $ def
371+ & initialAttributes .~ "type" =: "checkbox"
372+ & inputElementConfig_setChecked .~ setChecked
373+ performEvent_ $ liftIO . writeRef checkedByUIRef <$> _inputElement_checkedChange e
374+ performEvent_ $ liftIO . writeRef checkedRef <$> updated (_inputElement_checked e)
375+ it "captures file uploads" $ runWD $ do
376+ filesRef :: IORef [Text] <- newRef []
377+ let uploadFile = do
378+ e <- findElemWithRetry $ WD.ByTag "input"
379+ path <- liftIO $ writeSystemTempFile "testFile" "file contents"
380+ WD.sendKeys (T.pack path) e
381+ WD.click =<< findElemWithRetry (WD.ByTag "button")
382+ liftIO $ removeFile path
383+ readRef filesRef `shouldBeWithRetryM` [T.pack $ FilePath.takeFileName path]
384+ testWidget (pure ()) uploadFile $ do
385+ e <- inputElement $ def & initialAttributes .~ "type" =: "file"
386+ click <- button "save"
387+ prerender_ (pure ()) $ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
388+ names <- liftJSM $ traverse File.getName fs
389+ liftIO $ writeRef filesRef names
390+
391+ describe "hydration/immediate" $ session' $ do
392+ it "captures user input after switchover" $ runWD $ do
393+ inputRef :: IORef Text <- newRef ""
394+ let checkValue = do
395+ WD.sendKeys "hello world" =<< findElemWithRetry (WD.ByTag "input")
396+ WD.click =<< findElemWithRetry (WD.ByTag "button")
397+ readRef inputRef `shouldBeWithRetryM` "hello world"
398+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
399+ e <- inputElement def
400+ click <- button "save"
401+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
402+ it "sets focus appropriately" $ runWD $ do
403+ focusRef <- newRef False
404+ let checkValue = do
405+ readRef focusRef `shouldBeWithRetryM` False
406+ e <- findElemWithRetry $ WD.ByTag "input"
407+ WD.click e
408+ readRef focusRef `shouldBeWithRetryM` True
409+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
410+ e <- inputElement def
411+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_inputElement_hasFocus e)
412+ it "sets value appropriately" $ runWD $ do
413+ valueByUIRef :: IORef Text <- newRef ""
414+ valueRef :: IORef Text <- newRef ""
415+ setValueChan :: Chan Text <- liftIO newChan
416+ let checkValue = do
417+ readRef valueByUIRef `shouldBeWithRetryM` ""
418+ readRef valueRef `shouldBeWithRetryM` ""
419+ e <- findElemWithRetry $ WD.ByTag "input"
420+ WD.sendKeys "hello" e
421+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
422+ readRef valueRef `shouldBeWithRetryM` "hello"
423+ liftIO $ writeChan setValueChan "world"
424+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
425+ readRef valueRef `shouldBeWithRetryM` "world"
426+ testWidget (pure ()) checkValue $ do
427+ update <- triggerEventWithChan setValueChan
428+ prerender_ (pure ()) $ do
429+ e <- inputElement $ def & inputElementConfig_setValue .~ update
430+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _inputElement_input e
431+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
432+ it "sets checked appropriately" $ runWD $ do
433+ checkedByUIRef <- newRef False
434+ checkedRef <- newRef False
435+ setCheckedChan <- liftIO newChan
436+ let checkValue = do
437+ readRef checkedByUIRef `shouldBeWithRetryM` False
438+ readRef checkedRef `shouldBeWithRetryM` False
439+ e <- findElemWithRetry $ WD.ByTag "input"
440+ WD.moveToCenter e
441+ WD.click e
442+ readRef checkedByUIRef `shouldBeWithRetryM` True
443+ readRef checkedRef `shouldBeWithRetryM` True
444+ liftIO $ writeChan setCheckedChan False
445+ readRef checkedByUIRef `shouldBeWithRetryM` True
446+ readRef checkedRef `shouldBeWithRetryM` False
447+ testWidget (pure ()) checkValue $ do
448+ setChecked <- triggerEventWithChan setCheckedChan
449+ prerender_ (pure ()) $ do
450+ e <- inputElement $ def
451+ & initialAttributes .~ "type" =: "checkbox"
452+ & inputElementConfig_setChecked .~ setChecked
453+ performEvent_ $ liftIO . writeRef checkedByUIRef <$> _inputElement_checkedChange e
454+ performEvent_ $ liftIO . writeRef checkedRef <$> updated (_inputElement_checked e)
455+ it "captures file uploads" $ runWD $ do
456+ filesRef :: IORef [Text] <- newRef []
457+ let uploadFile = do
458+ e <- findElemWithRetry $ WD.ByTag "input"
459+ path <- liftIO $ writeSystemTempFile "testFile" "file contents"
460+ WD.sendKeys (T.pack path) e
461+ WD.click =<< findElemWithRetry (WD.ByTag "button")
462+ liftIO $ removeFile path
463+ readRef filesRef `shouldBeWithRetryM` [T.pack $ FilePath.takeFileName path]
464+ testWidget (pure ()) uploadFile $ prerender_ (pure ()) $ do
465+ e <- inputElement $ def & initialAttributes .~ "type" =: "file"
466+ click <- button "save"
467+ performEvent_ $ ffor (tag (current (_inputElement_files e)) click) $ \fs -> do
468+ names <- liftJSM $ traverse File.getName fs
469+ liftIO $ writeRef filesRef names
470+
471+ describe "textAreaElement" $ do
472+ describe "hydration" $ session' $ do
473+ it "doesn't wipe user input when switching over" $ runWD $ do
474+ inputRef <- newRef ("" :: Text)
475+ testWidget'
476+ (do
477+ e <- findElemWithRetry $ WD.ByTag "textarea"
478+ WD.sendKeys "hello world" e
479+ pure e)
480+ (\e -> do
481+ WD.attr e "value" `shouldBeWithRetryM` Just "hello world"
482+ WD.click <=< findElemWithRetry $ WD.ByTag "button"
483+ readRef inputRef `shouldBeWithRetryM` "hello world"
484+ ) $ do
485+ e <- textAreaElement def
486+ click <- button "save"
487+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
488+ it "captures user input after switchover" $ runWD $ do
489+ inputRef <- newRef ("" :: Text)
490+ let checkValue = do
491+ WD.sendKeys "hello world" =<< findElemWithRetry (WD.ByTag "textarea")
492+ WD.click =<< findElemWithRetry (WD.ByTag "button")
493+ readRef inputRef `shouldBeWithRetryM` "hello world"
494+ testWidget (pure ()) checkValue $ do
495+ e <- textAreaElement def
496+ click <- button "save"
497+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
498+ it "sets focus appropriately" $ runWD $ do
499+ focusRef <- newRef False
500+ let checkValue = do
501+ readRef focusRef `shouldBeWithRetryM` False
502+ e <- findElemWithRetry $ WD.ByTag "textarea"
503+ WD.click e
504+ readRef focusRef `shouldBeWithRetryM` True
505+ testWidget (pure ()) checkValue $ do
506+ e <- textAreaElement def
507+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_textAreaElement_hasFocus e)
508+ it "sets focus when focus occurs before hydration" $ runWD $ do
509+ focusRef <- newRef False
510+ let setup = do
511+ e <- findElemWithRetry $ WD.ByTag "textarea"
512+ WD.click e
513+ ((== e) <$> WD.activeElem) `shouldBeWithRetryM` True
514+ readRef focusRef `shouldBeWithRetryM` False
515+ check = readRef focusRef `shouldBeWithRetryM` True
516+ testWidget setup check $ do
517+ e <- textAreaElement def
518+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_textAreaElement_hasFocus e)
519+ it "sets value appropriately" $ runWD $ do
520+ valueByUIRef <- newRef ("" :: Text)
521+ valueRef <- newRef ("" :: Text)
522+ setValueChan :: Chan Text <- liftIO newChan
523+ let checkValue = do
524+ readRef valueByUIRef `shouldBeWithRetryM` ""
525+ readRef valueRef `shouldBeWithRetryM` ""
526+ e <- findElemWithRetry $ WD.ByTag "textarea"
527+ WD.sendKeys "hello" e
528+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
529+ readRef valueRef `shouldBeWithRetryM` "hello"
530+ liftIO $ writeChan setValueChan "world"
531+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
532+ readRef valueRef `shouldBeWithRetryM` "world"
533+ testWidget (pure ()) checkValue $ do
534+ setValue' <- triggerEventWithChan setValueChan
535+ e <- textAreaElement $ def { _textAreaElementConfig_setValue = Just setValue' }
536+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _textAreaElement_input e
537+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
538+
539+ describe "hydration/immediate" $ session' $ do
540+ it "captures user input after switchover" $ runWD $ do
541+ inputRef :: IORef Text <- newRef ""
542+ let checkValue = do
543+ WD.sendKeys "hello world" <=< findElemWithRetry $ WD.ByTag "textarea"
544+ WD.click <=< findElemWithRetry $ WD.ByTag "button"
545+ readRef inputRef `shouldBeWithRetryM` "hello world"
546+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
547+ e <- textAreaElement def
548+ click <- button "save"
549+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (value e)) click
550+ it "sets focus appropriately" $ runWD $ do
551+ focusRef <- newRef False
552+ let checkValue = do
553+ readRef focusRef `shouldBeWithRetryM` False
554+ e <- findElemWithRetry $ WD.ByTag "textarea"
555+ WD.click e
556+ readRef focusRef `shouldBeWithRetryM` True
557+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
558+ e <- textAreaElement def
559+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_textAreaElement_hasFocus e)
560+ it "sets value appropriately" $ runWD $ do
561+ valueByUIRef :: IORef Text <- newRef ""
562+ valueRef :: IORef Text <- newRef ""
563+ setValueChan :: Chan Text <- liftIO newChan
564+ let checkValue = do
565+ readRef valueByUIRef `shouldBeWithRetryM` ""
566+ readRef valueRef `shouldBeWithRetryM` ""
567+ e <- findElemWithRetry $ WD.ByTag "textarea"
568+ WD.sendKeys "hello" e
569+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
570+ readRef valueRef `shouldBeWithRetryM` "hello"
571+ liftIO $ writeChan setValueChan "world"
572+ readRef valueByUIRef `shouldBeWithRetryM` "hello"
573+ readRef valueRef `shouldBeWithRetryM` "world"
574+ testWidget (pure ()) checkValue $ do
575+ setValue' <- triggerEventWithChan setValueChan
576+ prerender_ (pure ()) $ do
577+ e <- textAreaElement $ def { _textAreaElementConfig_setValue = Just setValue' }
578+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _textAreaElement_input e
579+ performEvent_ $ liftIO . writeRef valueRef <$> updated (value e)
580+
581+ describe "selectElement" $ do
582+ let options :: DomBuilder t m => m ()
583+ options = do
584+ elAttr "option" ("value" =: "one" <> "id" =: "one") $ text "one"
585+ elAttr "option" ("value" =: "two" <> "id" =: "two") $ text "two"
586+ elAttr "option" ("value" =: "three" <> "id" =: "three") $ text "three"
587+ describe "hydration" $ session' $ do
588+ it "sets initial value correctly" $ runWD $ do
589+ inputRef <- newRef ("" :: Text)
590+ let setup = do
591+ e <- findElemWithRetry $ WD.ByTag "select"
592+ assertAttr e "value" (Just "three")
593+ WD.click =<< findElemWithRetry (WD.ById "two")
594+ pure e
595+ check e = do
596+ assertAttr e "value" (Just "two")
597+ readRef inputRef `shouldBeWithRetryM` "three"
598+ WD.click =<< findElemWithRetry (WD.ByTag "button")
599+ assertAttr e "value" (Just "two")
600+ readRef inputRef `shouldBeWithRetryM` "two"
601+ testWidget' setup check $ do
602+ (e, ()) <- selectElement (def { _selectElementConfig_initialValue = "three" }) options
603+ click <- button "save"
604+ liftIO . writeRef inputRef <=< sample $ current $ _selectElement_value e
605+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (_selectElement_value e)) click
606+ it "captures user input after switchover" $ runWD $ do
607+ inputRef <- newRef ("" :: Text)
608+ let checkValue = do
609+ e <- findElemWithRetry $ WD.ByTag "select"
610+ assertAttr e "value" (Just "one")
611+ WD.click =<< findElemWithRetry (WD.ById "two")
612+ assertAttr e "value" (Just "two")
613+ WD.click =<< findElemWithRetry (WD.ByTag "button")
614+ readRef inputRef `shouldBeWithRetryM` "two"
615+ testWidget (pure ()) checkValue $ do
616+ (e, ()) <- selectElement def options
617+ click <- button "save"
618+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (_selectElement_value e)) click
619+ it "sets focus appropriately" $ runWD $ do
620+ focusRef <- newRef False
621+ let checkValue = do
622+ readRef focusRef `shouldBeWithRetryM` False
623+ e <- findElemWithRetry $ WD.ByTag "select"
624+ WD.click e
625+ readRef focusRef `shouldBeWithRetryM` True
626+ testWidget (pure ()) checkValue $ do
627+ (e, ()) <- selectElement def options
628+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_selectElement_hasFocus e)
629+ it "sets focus when focus occurs before hydration" $ runWD $ do
630+ focusRef <- newRef False
631+ let setup = do
632+ e <- findElemWithRetry $ WD.ByTag "select"
633+ WD.click e
634+ ((== e) <$> WD.activeElem) `shouldBeWithRetryM` True
635+ readRef focusRef `shouldBeWithRetryM` False
636+ check = readRef focusRef `shouldBeWithRetryM` True
637+ testWidget setup check $ do
638+ (e, ()) <- selectElement def options
639+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_selectElement_hasFocus e)
640+ it "sets value appropriately" $ runWD $ do
641+ valueByUIRef <- newRef ("" :: Text)
642+ valueRef <- newRef ("" :: Text)
643+ setValueChan :: Chan Text <- liftIO newChan
644+ let checkValue = do
645+ e <- findElemWithRetry $ WD.ByTag "select"
646+ assertAttr e "value" (Just "one")
647+ readRef valueByUIRef `shouldBeWithRetryM` "one"
648+ readRef valueRef `shouldBeWithRetryM` "one"
649+ WD.click <=< findElemWithRetry $ WD.ById "two"
650+ readRef valueByUIRef `shouldBeWithRetryM` "two"
651+ readRef valueRef `shouldBeWithRetryM` "two"
652+ liftIO $ writeChan setValueChan "three"
653+ readRef valueByUIRef `shouldBeWithRetryM` "two"
654+ readRef valueRef `shouldBeWithRetryM` "three"
655+ testWidget (pure ()) checkValue $ do
656+ setValue' <- triggerEventWithChan setValueChan
657+ (e, ()) <- selectElement def { _selectElementConfig_setValue = Just setValue' } options
658+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _selectElement_change e
659+ performEvent_ $ liftIO . writeRef valueRef <$> updated (_selectElement_value e)
660+
661+ describe "hydration/immediate" $ session' $ do
662+ it "captures user input after switchover" $ runWD $ do
663+ inputRef :: IORef Text <- newRef ""
664+ let checkValue = do
665+ WD.click <=< findElemWithRetry $ WD.ById "two"
666+ WD.click <=< findElemWithRetry $ WD.ByTag "button"
667+ readRef inputRef `shouldBeWithRetryM` "two"
668+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
669+ (e, ()) <- selectElement def options
670+ click <- button "save"
671+ performEvent_ $ liftIO . writeRef inputRef <$> tag (current (_selectElement_value e)) click
672+ it "sets focus appropriately" $ runWD $ do
673+ focusRef <- newRef False
674+ let checkValue = do
675+ readRef focusRef `shouldBeWithRetryM` False
676+ e <- findElemWithRetry $ WD.ByTag "select"
677+ WD.click e
678+ readRef focusRef `shouldBeWithRetryM` True
679+ testWidget (pure ()) checkValue $ prerender_ (pure ()) $ do
680+ (e, ()) <- selectElement def options
681+ performEvent_ $ liftIO . writeRef focusRef <$> updated (_selectElement_hasFocus e)
682+ it "has correct initial value" $ runWD $ do
683+ valueRef :: IORef Text <- newRef ""
684+ let checkValue = readRef valueRef `shouldBeWithRetryM` "one"
685+ testWidget (pure ()) checkValue $ do
686+ prerender_ (pure ()) $ do
687+ (e, ()) <- selectElement def { _selectElementConfig_initialValue = "one" } options
688+ liftIO . writeRef valueRef <=< sample $ current $ _selectElement_value e
689+ it "sets value appropriately" $ runWD $ do
690+ valueByUIRef :: IORef Text <- newRef ""
691+ valueRef :: IORef Text <- newRef ""
692+ setValueChan :: Chan Text <- liftIO newChan
693+ let checkValue = do
694+ WD.click =<< findElemWithRetry (WD.ById "two")
695+ readRef valueByUIRef `shouldBeWithRetryM` "two"
696+ readRef valueRef `shouldBeWithRetryM` "two"
697+ liftIO $ writeChan setValueChan "three"
698+ readRef valueByUIRef `shouldBeWithRetryM` "two"
699+ readRef valueRef `shouldBeWithRetryM` "three"
700+ testWidget (pure ()) checkValue $ do
701+ setValue' <- triggerEventWithChan setValueChan
702+ prerender_ (pure ()) $ do
703+ (e, ()) <- selectElement def { _selectElementConfig_setValue = Just setValue' } options
704+ performEvent_ $ liftIO . writeRef valueByUIRef <$> _selectElement_change e
705+ performEvent_ $ liftIO . writeRef valueRef <$> updated (_selectElement_value e)
706+
707+ describe "prerender" $ session' $ do
708+ it "works in simple case" $ runWD $ do
709+ testWidget (checkBodyText "One") (checkBodyText "Two") $ do
710+ prerender_ (text "One") (text "Two")
711+ it "removes element correctly" $ runWD $ do
712+ testWidget' (findElemWithRetry $ WD.ByTag "span") elementShouldBeRemoved $ do
713+ prerender_ (el "span" $ text "One") (text "Two")
714+ it "can be nested in server widget" $ runWD $ do
715+ testWidget (checkBodyText "One") (checkBodyText "Three") $ do
716+ prerender_ (prerender_ (text "One") (text "Two")) (text "Three")
717+ it "can be nested in client widget" $ runWD $ do
718+ testWidget (checkBodyText "One") (checkBodyText "Three") $ do
719+ prerender_ (text "One") (prerender_ (text "Two") (text "Three"))
720+ it "works with prerender_ siblings" $ runWD $ do
721+ testWidget
722+ (checkTextInId "a1" "One" >> checkTextInId "b1" "Three" >> checkTextInId "c1" "Five")
723+ (checkTextInId "a2" "Two" >> checkTextInId "b2" "Four" >> checkTextInId "c2" "Six") $ do
724+ prerender_ (divId "a1" $ text "One") (divId "a2" $ text "Two")
725+ prerender_ (divId "b1" $ text "Three") (divId "b2" $ text "Four")
726+ prerender_ (divId "c1" $ text "Five") (divId "c2" $ text "Six")
727+ it "works inside other element" $ runWD $ do
728+ testWidget (checkTextInTag "div" "One") (checkTextInTag "div" "Two") $ do
729+ el "div" $ prerender_ (text "One") (text "Two")
730+-- TODO re-enable this
731+-- it "places fences and removes them" $ runWD $ do
732+-- testWidget'
733+-- (do
734+-- scripts <- WD.findElems $ WD.ByTag "script"
735+-- filterM (\s -> maybe False (\t -> "prerender" `T.isPrefixOf` t) <$> WD.attr s "type") scripts
736+-- )
737+-- (traverse_ elementShouldBeRemoved)
738+-- (el "span" $ prerender_ (text "One") (text "Two"))
739+ it "postBuild works on server side" $ runWD $ do
740+ lock :: MVar () <- liftIO newEmptyMVar
741+ testWidget (liftIO $ takeMVar lock) (pure ()) $ do
742+ prerender_ (do
743+ pb <- getPostBuild
744+ performEvent_ $ liftIO (putMVar lock ()) <$ pb) blank
745+ it "postBuild works on client side" $ runWD $ do
746+ lock :: MVar () <- liftIO newEmptyMVar
747+ testWidget (pure ()) (liftIO $ takeMVar lock) $ do
748+ prerender_ blank $ do
749+ pb <- getPostBuild
750+ performEvent_ $ liftIO (putMVar lock ()) <$ pb
751+ it "result Dynamic is updated *after* switchover" $ runWD $ do
752+ let static = checkBodyText "PostBuild"
753+ check = checkBodyText "Client"
754+ testWidget static check $ void $ do
755+ d <- prerender (pure "Initial") (pure "Client")
756+ pb <- getPostBuild
757+ initial <- sample $ current d
758+ textNode $ TextNodeConfig initial $ Just $ leftmost [updated d, "PostBuild" <$ pb]
759+ -- This essentially checks that the client IO runs *after* switchover/postBuild,
760+ -- thus can't create conflicting DOM
761+ it "can't exploit IO to break hydration" $ runWD $ do
762+ let static = checkBodyText "Initial"
763+ testWidgetStatic static $ void $ do
764+ ref <- liftIO $ newRef "Initial"
765+ prerender_ (pure ()) (liftIO $ writeRef ref "Client")
766+ text <=< liftIO $ readRef ref
767+ -- As above, so below
768+ it "can't exploit triggerEvent to break hydration" $ runWD $ do
769+ let static = checkBodyText "Initial"
770+ check = checkBodyText "Client"
771+ testWidget static check $ void $ do
772+ (e, trigger) <- newTriggerEvent
773+ prerender_ (pure ()) (liftIO $ trigger "Client")
774+ textNode $ TextNodeConfig "Initial" $ Just e
775+
776+ describe "runWithReplace" $ session' $ do
777+ it "works" $ runWD $ do
778+ replaceChan :: Chan Text <- liftIO newChan
779+ let setup = findElemWithRetry $ WD.ByTag "div"
780+ check ssr = do
781+ -- Check that the original element still exists and has the correct text
782+ shouldContainText "two" ssr
783+ liftIO $ writeChan replaceChan "three"
784+ elementShouldBeRemoved ssr
785+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
786+ testWidget' setup check $ do
787+ replace <- triggerEventWithChan replaceChan
788+ pb <- getPostBuild
789+ void $ runWithReplace (el "div" $ text "one") $ leftmost
790+ [ el "div" (text "two") <$ pb
791+ , el "span" . text <$> replace
792+ ]
793+ it "can be nested in initial widget" $ runWD $ do
794+ replaceChan :: Chan Text <- liftIO newChan
795+ let setup = findElemWithRetry $ WD.ByTag "div"
796+ check ssr = do
797+ -- Check that the original element still exists and has the correct text
798+ shouldContainText "two" ssr
799+ liftIO $ writeChan replaceChan "three"
800+ elementShouldBeRemoved ssr
801+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
802+ testWidget' setup check $ void $ flip runWithReplace never $ do
803+ replace <- triggerEventWithChan replaceChan
804+ pb <- getPostBuild
805+ void $ runWithReplace (el "div" $ text "one") $ leftmost
806+ [ el "div" (text "two") <$ pb
807+ , el "span" . text <$> replace
808+ ]
809+ it "can be nested in postBuild widget" $ runWD $ do
810+ replaceChan <- liftIO newChan
811+ let setup = findElemWithRetry $ WD.ByTag "div"
812+ check ssr = do
813+ -- Check that the original element still exists and has the correct text
814+ shouldContainText "two" ssr
815+ liftIO $ writeChan replaceChan "three"
816+ elementShouldBeRemoved ssr
817+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
818+ testWidget' setup check $ void $ do
819+ pb <- getPostBuild
820+ runWithReplace (pure ()) $ ffor pb $ \() -> do
821+ replace <- triggerEventWithChan replaceChan
822+ pb' <- getPostBuild
823+ void $ runWithReplace (el "div" $ text "one") $ leftmost
824+ [ el "div" (text "two") <$ pb'
825+ , el "span" . text <$> replace
826+ ]
827+ it "postBuild works in replaced widget" $ runWD $ do
828+ replaceChan <- liftIO newChan
829+ pbLock <- liftIO newEmptyMVar
830+ let check = liftIO $ do
831+ writeChan replaceChan ()
832+ takeMVar pbLock
833+ testWidget (pure ()) check $ void $ do
834+ replace <- triggerEventWithChan replaceChan
835+ runWithReplace (pure ()) $ ffor replace $ \() -> do
836+ pb <- getPostBuild
837+ performEvent_ $ liftIO (putMVar pbLock ()) <$ pb
838+ it "can be nested in event widget" $ runWD $ do
839+ replaceChan1 :: Chan Text <- liftIO newChan
840+ replaceChan2 :: Chan Text <- liftIO newChan
841+ lock :: MVar () <- liftIO newEmptyMVar
842+ let check = do
843+ shouldContainText "" =<< findElemWithRetry (WD.ByTag "body")
844+ liftIO $ do
845+ writeChan replaceChan1 "one"
846+ takeMVar lock
847+ one <- findElemWithRetry $ WD.ByTag "div"
848+ shouldContainText "pb" one
849+ liftIO $ writeChan replaceChan2 "two"
850+ elementShouldBeRemoved one
851+ shouldContainText "two" =<< findElemWithRetry (WD.ByTag "span")
852+ testWidget (pure ()) check $ void $ do
853+ replace1 <- triggerEventWithChan replaceChan1
854+ runWithReplace (pure ()) $ ffor replace1 $ \r1 -> do
855+ replace2 <- triggerEventWithChan replaceChan2
856+ pb <- getPostBuild
857+ performEvent_ $ liftIO (putMVar lock ()) <$ pb
858+ void $ runWithReplace (el "div" $ text r1) $ leftmost
859+ [ el "span" . text <$> replace2
860+ , el "div" (text "pb") <$ pb
861+ ]
862+ it "prerender works in replaced widget" $ runWD $ do
863+ replaceChan <- liftIO newChan
864+ lock :: MVar () <- liftIO newEmptyMVar
865+ pbLock :: MVar () <- liftIO newEmptyMVar
866+ let check = do
867+ liftIO $ do
868+ writeChan replaceChan ()
869+ takeMVar lock
870+ takeMVar pbLock
871+ testWidget (pure ()) check $ void $ do
872+ replace <- triggerEventWithChan replaceChan
873+ runWithReplace (pure ()) $ ffor replace $ \() -> do
874+ prerender_ (pure ()) $ do
875+ liftIO $ putMVar lock ()
876+ pb <- getPostBuild
877+ performEvent_ $ liftIO (putMVar pbLock ()) <$ pb
878+ it "prerender returns in immediate mode" $ runWD $ do
879+ replaceChan <- liftIO newChan
880+ pbLock <- liftIO newEmptyMVar
881+ let check = liftIO $ do
882+ writeChan replaceChan ()
883+ takeMVar pbLock
884+ testWidget (pure ()) check $ void $ do
885+ replace <- triggerEventWithChan replaceChan
886+ runWithReplace (pure ()) $ ffor replace $ \() -> do
887+ prerender_ (pure ()) (pure ())
888+ liftIO $ putMVar pbLock ()
889+ it "works in immediate mode (RHS of prerender)" $ runWD $ do
890+ replaceChan :: Chan Text <- liftIO newChan
891+ let check = do
892+ checkBodyText "one"
893+ one <- findElemWithRetry $ WD.ByTag "div"
894+ shouldContainText "one" one
895+ liftIO $ writeChan replaceChan "pb"
896+ elementShouldBeRemoved one
897+ shouldContainText "pb" =<< findElemWithRetry (WD.ByTag "span")
898+ testWidget (pure ()) check $ void $ do
899+ replace <- triggerEventWithChan replaceChan
900+ prerender_ blank $ do
901+ void $ runWithReplace (el "div" $ text "one") $ el "span" . text <$> replace
902+ it "works with postBuild in immediate mode (RHS of prerender)" $ runWD $ do
903+ replaceChan :: Chan Text <- liftIO newChan
904+ let check = do
905+ checkBodyText "two"
906+ two <- findElemWithRetry $ WD.ByTag "div"
907+ shouldContainText "two" two
908+ liftIO $ writeChan replaceChan "three"
909+ elementShouldBeRemoved two
910+ shouldContainText "three" =<< findElemWithRetry (WD.ByTag "span")
911+ testWidget (pure ()) check $ void $ do
912+ replace <- triggerEventWithChan replaceChan
913+ prerender_ blank $ do
914+ pb <- getPostBuild
915+ void $ runWithReplace (el "div" $ text "one") $ leftmost
916+ [ el "div" (text "two") <$ pb
917+ , el "span" . text <$> replace
918+ ]
919+
920+ describe "traverseDMapWithKeyWithAdjust" $ session' $ do
921+ let widget :: DomBuilder t m => DKey a -> Identity a -> m (Identity a)
922+ widget k (Identity v) = elAttr "li" ("id" =: textKey k) $ do
923+ elClass "span" "key" $ text $ textKey k
924+ elClass "span" "value" $ text $ T.pack $ has @Show k $ show v
925+ pure (Identity v)
926+ checkItem :: WD.Element -> Text -> Text -> WD ()
927+ checkItem li k v = do
928+ putStrLnDebug "checkItem"
929+ shouldContainTextNoRetry k =<< WD.findElemFrom li (WD.ByClass "key")
930+ shouldContainTextNoRetry v =<< WD.findElemFrom li (WD.ByClass "value")
931+ checkInitialItems dm xs = do
932+ putStrLnDebug "checkInitialItems"
933+ liftIO $ assertEqual "Wrong amount of items in DOM" (DMap.size dm) (length xs)
934+ forM_ (zip xs (DMap.toList dm)) $ \(e, k :=> Identity v) -> checkItem e (textKey k) (T.pack $ has @Show k $ show v)
935+ getAndCheckInitialItems dm = withRetry $ do
936+ putStrLnDebug "getAndCheckInitialItems"
937+ xs <- WD.findElems (WD.ByTag "li")
938+ checkInitialItems dm xs
939+ pure xs
940+ checkRemoval chan k = do
941+ putStrLnDebug "checkRemoval"
942+ e <- findElemWithRetry (WD.ById $ textKey k)
943+ liftIO $ writeChan chan $ PatchDMap $ DMap.singleton k (ComposeMaybe Nothing)
944+ elementShouldBeRemoved e
945+ checkReplace chan k v = do
946+ putStrLnDebug "checkReplace"
947+ e <- findElemWithRetry (WD.ById $ textKey k)
948+ liftIO $ writeChan chan $ PatchDMap $ DMap.singleton k (ComposeMaybe $ Just $ Identity v)
949+ elementShouldBeRemoved e
950+ e' <- findElemWithRetry $ WD.ById $ textKey k
951+ checkItem e' (textKey k) (T.pack $ show v)
952+ checkInsert chan k v = do
953+ putStrLnDebug "checkInsert"
954+ liftIO $ writeChan chan $ PatchDMap $ DMap.singleton k (ComposeMaybe $ Just $ Identity v)
955+ e <- findElemWithRetry (WD.ById $ textKey k)
956+ checkItem e (textKey k) (T.pack $ show v)
957+ postBuildPatch = PatchDMap $ DMap.fromList [Key_Char :=> ComposeMaybe Nothing, Key_Bool :=> ComposeMaybe (Just $ Identity True)]
958+ it "doesn't replace elements at switchover, can delete/update/insert" $ runWD $ do
959+ chan <- liftIO newChan
960+ let static :: WD [WD.Element]
961+ static = getAndCheckInitialItems keyMap
962+ check :: [WD.Element] -> WD ()
963+ check xs = do
964+ checkInitialItems keyMap xs
965+ checkRemoval chan Key_Int
966+ checkReplace chan Key_Char 'B'
967+ checkInsert chan Key_Bool True
968+ testWidget' static check $ do
969+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap =<< triggerEventWithChan chan
970+ liftIO $ dmap `H.shouldBe` keyMap
971+ it "handles postBuild correctly" $ runWD $ do
972+ chan <- liftIO newChan
973+ let static = getAndCheckInitialItems $ applyAlways postBuildPatch keyMap
974+ check xs = do
975+ withRetry $ checkInitialItems (applyAlways postBuildPatch keyMap) xs
976+ checkRemoval chan Key_Int
977+ checkInsert chan Key_Char 'B'
978+ checkReplace chan Key_Bool True
979+ testWidget' static check $ void $ do
980+ pb <- getPostBuild
981+ replace <- triggerEventWithChan chan
982+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap $ leftmost [postBuildPatch <$ pb, replace]
983+ liftIO $ dmap `H.shouldBe` keyMap
984+ it "can delete/update/insert when built in prerender" $ runWD $ do
985+ chan <- liftIO newChan
986+ let check = do
987+ _ <- getAndCheckInitialItems keyMap
988+ checkRemoval chan Key_Int
989+ checkReplace chan Key_Char 'B'
990+ checkInsert chan Key_Bool True
991+ testWidget (pure ()) check $ do
992+ replace <- triggerEventWithChan chan
993+ prerender_ (pure ()) $ do
994+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap replace
995+ liftIO $ dmap `H.shouldBe` keyMap
996+ it "can delete/update/insert when built in immediate mode" $ runWD $ do
997+ chan <- liftIO newChan
998+ let check = do
999+ _ <- getAndCheckInitialItems keyMap
1000+ checkRemoval chan Key_Int
1001+ checkReplace chan Key_Char 'B'
1002+ checkInsert chan Key_Bool True
1003+ testWidget (pure ()) check $ void $ do
1004+ pb <- getPostBuild
1005+ runWithReplace (pure ()) $ ffor pb $ \() -> void $ do
1006+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap =<< triggerEventWithChan chan
1007+ liftIO $ dmap `H.shouldBe` keyMap
1008+ -- Should be fixed by prerender changes!
1009+ it "handles postBuild correctly in prerender" $ runWD $ do
1010+ chan <- liftIO newChan
1011+ let check = do
1012+ _ <- getAndCheckInitialItems $ applyAlways postBuildPatch keyMap
1013+ checkRemoval chan Key_Int
1014+ checkInsert chan Key_Char 'B'
1015+ checkReplace chan Key_Bool True
1016+ testWidget (pure ()) check $ do
1017+ replace <- triggerEventWithChan chan
1018+ prerender_ (pure ()) $ void $ do
1019+ pb <- getPostBuild
1020+ (dmap, _evt) <- traverseDMapWithKeyWithAdjust widget keyMap $ leftmost [postBuildPatch <$ pb, replace]
1021+ liftIO $ dmap `H.shouldBe` keyMap
1022+
1023+ describe "traverseIntMapWithKeyWithAdjust" $ session' $ do
1024+ let textKeyInt k = "key" <> T.pack (show k)
1025+ intMap = IntMap.fromList
1026+ [ (1, "one")
1027+ , (2, "two")
1028+ , (3, "three")
1029+ ]
1030+ let widget :: DomBuilder t m => IntMap.Key -> Text -> m Text
1031+ widget k v = elAttr "li" ("id" =: textKeyInt k) $ do
1032+ elClass "span" "key" $ text $ textKeyInt k
1033+ elClass "span" "value" $ text v
1034+ pure v
1035+ checkItem :: WD.Element -> Text -> Text -> WD ()
1036+ checkItem li k v = do
1037+ putStrLnDebug "checkItem"
1038+ shouldContainTextNoRetry k =<< WD.findElemFrom li (WD.ByClass "key")
1039+ shouldContainTextNoRetry v =<< WD.findElemFrom li (WD.ByClass "value")
1040+ checkInitialItems dm xs = do
1041+ putStrLnDebug "checkInitialItems"
1042+ liftIO $ assertEqual "Wrong amount of items in DOM" (IntMap.size dm) (length xs)
1043+ forM_ (zip xs (IntMap.toList dm)) $ \(e, (k, v)) -> checkItem e (textKeyInt k) v
1044+ getAndCheckInitialItems dm = withRetry $ do
1045+ putStrLnDebug "getAndCheckInitialItems"
1046+ xs <- WD.findElems (WD.ByTag "li")
1047+ checkInitialItems dm xs
1048+ pure xs
1049+ checkRemoval chan k = do
1050+ putStrLnDebug "checkRemoval"
1051+ e <- findElemWithRetry (WD.ById $ textKeyInt k)
1052+ liftIO $ writeChan chan $ PatchIntMap $ IntMap.singleton k Nothing
1053+ elementShouldBeRemoved e
1054+ checkReplace chan k v = do
1055+ putStrLnDebug "checkReplace"
1056+ e <- findElemWithRetry (WD.ById $ textKeyInt k)
1057+ liftIO $ writeChan chan $ PatchIntMap $ IntMap.singleton k $ Just v
1058+ elementShouldBeRemoved e
1059+ e' <- findElemWithRetry (WD.ById $ textKeyInt k)
1060+ withRetry $ checkItem e' (textKeyInt k) v
1061+ checkInsert chan k v = do
1062+ putStrLnDebug "checkInsert"
1063+ liftIO $ writeChan chan $ PatchIntMap $ IntMap.singleton k $ Just v
1064+ e <- findElemWithRetry (WD.ById $ textKeyInt k)
1065+ checkItem e (textKeyInt k) v
1066+ postBuildPatch = PatchIntMap $ IntMap.fromList [(2, Nothing), (3, Just "trois"), (4, Just "four")]
1067+ xit "doesn't replace elements at switchover, can delete/update/insert" $ runWD $ do
1068+ chan <- liftIO newChan
1069+ let static = getAndCheckInitialItems intMap
1070+ check xs = do
1071+ checkInitialItems intMap xs
1072+ checkRemoval chan 1
1073+ checkReplace chan 2 "deux"
1074+ checkInsert chan 4 "four"
1075+ testWidget' static check $ void $ do
1076+ (im, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap =<< triggerEventWithChan chan
1077+ liftIO $ im `H.shouldBe` intMap
1078+ xit "handles postBuild correctly" $ runWD $ do
1079+ chan <- liftIO newChan
1080+ let static = getAndCheckInitialItems $ applyAlways postBuildPatch intMap
1081+ check xs = do
1082+ withRetry $ checkInitialItems (applyAlways postBuildPatch intMap) xs
1083+ checkRemoval chan 1
1084+ checkInsert chan 2 "deux"
1085+ checkReplace chan 3 "trois"
1086+ testWidget' static check $ void $ do
1087+ pb <- getPostBuild
1088+ replace <- triggerEventWithChan chan
1089+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap $ leftmost [postBuildPatch <$ pb, replace]
1090+ liftIO $ dmap `H.shouldBe` intMap
1091+ xit "can delete/update/insert when built in prerender" $ runWD $ do
1092+ chan <- liftIO newChan
1093+ let check = do
1094+ _ <- getAndCheckInitialItems intMap
1095+ checkRemoval chan 1
1096+ checkReplace chan 2 "deux"
1097+ checkInsert chan 4 "quatre"
1098+ testWidget (pure ()) check $ do
1099+ replace <- triggerEventWithChan chan
1100+ prerender_ (pure ()) $ do
1101+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap replace
1102+ liftIO $ dmap `H.shouldBe` intMap
1103+ xit "can delete/update/insert when built in immediate mode" $ runWD $ do
1104+ chan <- liftIO newChan
1105+ let check = do
1106+ _ <- getAndCheckInitialItems intMap
1107+ checkRemoval chan 1
1108+ checkReplace chan 2 "deux"
1109+ checkInsert chan 4 "quatre"
1110+ testWidget (pure ()) check $ void $ do
1111+ pb <- getPostBuild
1112+ runWithReplace (pure ()) $ ffor pb $ \() -> void $ do
1113+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap =<< triggerEventWithChan chan
1114+ liftIO $ dmap `H.shouldBe` intMap
1115+ -- Should be fixed by prerender changes!
1116+ xit "handles postBuild correctly in prerender" $ runWD $ do
1117+ chan <- liftIO newChan
1118+ let check = do
1119+ _ <- getAndCheckInitialItems $ applyAlways postBuildPatch intMap
1120+ checkRemoval chan 1
1121+ checkInsert chan 2 "deux"
1122+ checkReplace chan 3 "trois"
1123+ testWidget (pure ()) check $ do
1124+ replace <- triggerEventWithChan chan
1125+ prerender_ (pure ()) $ void $ do
1126+ pb <- getPostBuild
1127+ (dmap, _evt) <- traverseIntMapWithKeyWithAdjust widget intMap $ leftmost [postBuildPatch <$ pb, replace]
1128+ liftIO $ dmap `H.shouldBe` intMap
1129+
1130+ describe "traverseDMapWithKeyWithAdjustWithMove" $ session' $ do
1131+ let widget :: DomBuilder t m => Key2 a -> Identity a -> m (Identity a)
1132+ widget k (Identity v) = elAttr "li" ("id" =: textKey2 k) $ do
1133+ elClass "span" "key" $ text $ textKey2 k
1134+ elClass "span" "value" $ text $ T.pack $ has @Show k $ show v
1135+ pure (Identity v)
1136+ textKey2 :: Key2 a -> Text
1137+ textKey2 = \case
1138+ Key2_Int i -> "i" <> T.pack (show i)
1139+ Key2_Char c -> "c" <> T.pack [c]
1140+ checkItem :: WD.Element -> Text -> Text -> WD ()
1141+ checkItem li k v = do
1142+ shouldContainTextNoRetry k =<< WD.findElemFrom li (WD.ByClass "key")
1143+ shouldContainTextNoRetry v =<< WD.findElemFrom li (WD.ByClass "value")
1144+ checkInitialItems dm xs = do
1145+ liftIO $ assertEqual "Wrong amount of items in DOM" (DMap.size dm) (length xs)
1146+ forM_ (zip xs (DMap.toList dm)) $ \(e, k :=> Identity v) -> checkItem e (textKey2 k) (T.pack $ has @Show k $ show v)
1147+ getAndCheckInitialItems dm = withRetry $ do
1148+ xs <- WD.findElems (WD.ByTag "li")
1149+ checkInitialItems dm xs
1150+ pure xs
1151+ moveSpec testMove = do
1152+ it "can insert an item" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 3 ==> 3]) $ \body chan -> do
1153+ shouldContainText (T.strip $ T.unlines ["i11","i33"]) body
1154+ liftIO $ writeChan chan (insertDMapKey (Key2_Int 2) 2)
1155+ shouldContainText (T.strip $ T.unlines ["i11","i22","i33"]) body
1156+ it "can delete an item" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 2 ==> 2, Key2_Int 3 ==> 3]) $ \body chan -> do
1157+ shouldContainText (T.strip $ T.unlines ["i11","i22","i33"]) body
1158+ liftIO $ writeChan chan (deleteDMapKey (Key2_Int 2))
1159+ shouldContainText (T.strip $ T.unlines ["i11","i33"]) body
1160+ it "can swap items" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 2 ==> 2, Key2_Int 3 ==> 3, Key2_Int 4 ==> 4]) $ \body chan -> do
1161+ shouldContainText (T.strip $ T.unlines ["i11","i22","i33","i44"]) body
1162+ liftIO $ writeChan chan (swapDMapKey (Key2_Int 2) (Key2_Int 3))
1163+ shouldContainText (T.strip $ T.unlines ["i11","i33","i22","i44"]) body
1164+-- TODO re-enable this
1165+-- it "can move items" $ runWD $ testMove (DMap.fromList [Key2_Int 1 ==> 1, Key2_Int 2 ==> 2, Key2_Int 3 ==> 3, Key2_Int 4 ==> 4]) $ \body chan -> do
1166+-- shouldContainText (T.strip $ T.unlines ["i11","i22","i33", "i44"]) body
1167+-- liftIO $ writeChan chan (moveDMapKey (Key2_Int 2) (Key2_Int 3))
1168+-- shouldContainText (T.strip $ T.unlines ["i11","i22","i44"]) body
1169+-- liftIO $ writeChan chan (moveDMapKey (Key2_Int 2) (Key2_Int 3)) -- attempt a move to nonexistent key should delete
1170+-- shouldContainText (T.strip $ T.unlines ["i11","i44"]) body
1171+-- liftIO $ writeChan chan (moveDMapKey (Key2_Int 2) (Key2_Int 3)) -- this causes a JSException in immediate builder too
1172+-- shouldContainText (T.strip $ T.unlines ["i11","i44"]) body
1173+-- liftIO $ writeChan chan (insertDMapKey (Key2_Int 2) 2) -- this step will fail if the above caused an exception, thus works as a proxy for detecting the exception given we can't catch it
1174+-- shouldContainText (T.strip $ T.unlines ["i11","i22","i44"]) body
1175+
1176+ describe "hydration" $ moveSpec $ \initMap test -> do
1177+ chan <- liftIO newChan
1178+ let static = getAndCheckInitialItems initMap
1179+ check xs = do
1180+ checkInitialItems initMap xs
1181+ body <- getBody
1182+ test body chan
1183+ testWidget' static check $ void $ do
1184+ (dmap, _evt) <- traverseDMapWithKeyWithAdjustWithMove widget initMap =<< triggerEventWithChan chan
1185+ liftIO $ assertEqual "DMap" initMap dmap
1186+
1187+ describe "hydration/immediate" $ moveSpec $ \initMap test -> do
1188+ chan <- liftIO newChan
1189+ replace <- liftIO newChan
1190+ lock <- liftIO newEmptyMVar
1191+ let check = do
1192+ liftIO $ do
1193+ writeChan replace ()
1194+ takeMVar lock
1195+ xs <- getAndCheckInitialItems initMap
1196+ checkInitialItems initMap xs
1197+ body <- getBody
1198+ test body chan
1199+ testWidget (pure ()) check $ void $ do
1200+ e <- triggerEventWithChan replace
1201+ runWithReplace (pure ()) $ ffor e $ \() -> void $ do
1202+ pb <- getPostBuild
1203+ performEvent_ $ liftIO (putMVar lock ()) <$ pb
1204+ (dmap, _evt) <- traverseDMapWithKeyWithAdjustWithMove widget initMap =<< triggerEventWithChan chan
1205+ liftIO $ assertEqual "DMap" initMap dmap
1206+
1207+ describe "hydrating invalid HTML" $ session' $ do
1208+ it "can hydrate list in paragraph" $ runWD $ do
1209+ let static = do
1210+ checkBodyText "before\ninner\nafter"
1211+ -- Two <p> tags should be present
1212+ [p1, p2] <- WD.findElems (WD.ByTag "p")
1213+ ol <- findElemWithRetry (WD.ByTag "ol")
1214+ shouldContainText "before" p1
1215+ shouldContainText "inner" ol
1216+ shouldContainText "" p2
1217+ pure (p1, ol, p2)
1218+ check (p1, ol, p2) = do
1219+ checkBodyText "before\ninner\nafter"
1220+ shouldContainText "before\ninner\nafter" p1
1221+ elementShouldBeRemoved ol
1222+ elementShouldBeRemoved p2
1223+ testWidget' static check $ do
1224+ -- This is deliberately invalid HTML, the browser will interpret it as
1225+ -- <p>before</p><ol>inner</ol>after<p></p>
1226+ el "p" $ do
1227+ text "before"
1228+ el "ol" $ text "inner"
1229+ text "after"
1230+
1231+data Selenium = Selenium
1232+ { _selenium_portNumber :: PortNumber
1233+ , _selenium_stopServer :: IO ()
1234+ }
1235+
1236+startSeleniumServer :: PortNumber -> IO (IO ())
1237+startSeleniumServer port = do
1238+ (_,_,_,ph) <- createProcess $ (proc "selenium-server" ["-port", show port])
1239+ { std_in = NoStream
1240+ , std_out = NoStream
1241+ , std_err = NoStream
1242+ }
1243+ return $ terminateProcess ph
1244+
1245+withSeleniumServer :: (Selenium -> IO ()) -> IO ()
1246+withSeleniumServer f = do
1247+ stopServer <- startSeleniumServer seleniumPort
1248+ threadDelay $ 1000 * 1000 * 2 -- TODO poll or wait on a a signal to block on
1249+ f $ Selenium
1250+ { _selenium_portNumber = seleniumPort
1251+ , _selenium_stopServer = stopServer
1252+ }
1253+
1254+triggerEventWithChan :: (Reflex t, TriggerEvent t m, Prerender js t m) => Chan a -> m (Event t a)
1255+triggerEventWithChan chan = do
1256+ (e, trigger) <- newTriggerEvent
1257+ -- In prerender because we only want to do this on the client
1258+ prerender_ (pure ()) $ void $ liftIO $ forkIO $ forever $ trigger =<< readChan chan
1259+ pure e
1260+
1261+shouldBeWithRetryM :: (Eq a, Show a) => WD a -> a -> WD ()
1262+shouldBeWithRetryM m expected = withRetry $ do
1263+ got <- m
1264+ got `shouldBe` expected
1265+
1266+assertAttr :: WD.Element -> Text -> Maybe Text -> WD ()
1267+assertAttr e k v = liftIO . assertEqual "Incorrect attribute value" v =<< WD.attr e k
1268+
1269+elementShouldBeRemoved :: WD.Element -> WD ()
1270+elementShouldBeRemoved e = withRetry $ do
1271+ try (WD.getText e) >>= \case
1272+ Left (WD.FailedCommand WD.StaleElementReference _) -> return ()
1273+ Left err -> throwM err
1274+ Right !_ -> liftIO $ assertFailure "Expected element to be removed, but it still exists"
1275+
1276+shouldContainText :: Text -> WD.Element -> WD ()
1277+shouldContainText t = withRetry . shouldContainTextNoRetry t
1278+
1279+shouldContainTextNoRetry :: Text -> WD.Element -> WD ()
1280+shouldContainTextNoRetry t = flip shouldBe t <=< WD.getText
1281+
1282+checkBodyText :: Text -> WD ()
1283+checkBodyText = checkTextInTag "body"
1284+
1285+checkTextInTag :: Text -> Text -> WD ()
1286+checkTextInTag t expected = do
1287+ e <- findElemWithRetry (WD.ByTag t)
1288+ shouldContainText expected e
1289+
1290+checkTextInId :: Text -> Text -> WD ()
1291+checkTextInId i expected = do
1292+ e <- findElemWithRetry (WD.ById i)
1293+ shouldContainText expected e
1294+
1295+findElemWithRetry :: Selector -> WD WD.Element
1296+findElemWithRetry = withRetry . WD.findElem
1297+
1298+getBody :: WD WD.Element
1299+getBody = WD.findElem $ WD.ByTag "body"
1300+
1301+withRetry :: forall a. WD a -> WD a
1302+withRetry a = wait 300
1303+ where wait :: Int -> WD a
1304+ wait 0 = a
1305+ wait n = try a >>= \case
1306+ Left (_ :: SomeException) -> do
1307+ liftIO $ threadDelay 100000
1308+ wait $ n - 1
1309+ Right v -> return v
1310+
1311+divId :: DomBuilder t m => Text -> m a -> m a
1312+divId i = elAttr "div" ("id" =: i)
1313+
1314+type TestWidget js t m = (DomBuilder t m, MonadHold t m, PostBuild t m, Prerender js t m, PerformEvent t m, TriggerEvent t m, MonadFix m, MonadIO (Performable m), MonadIO m)
1315+
1316+testWidgetStaticDebug
1317+ :: Bool
1318+ -> WD b
1319+ -- ^ Webdriver commands to run before JS runs and after hydration switchover
1320+ -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ())
1321+ -- ^ Widget we are testing
1322+ -> WD b
1323+testWidgetStaticDebug withDebugging w = testWidgetDebug withDebugging (void w) w
1324+
1325+-- | TODO: do something about JSExceptions not causing tests to fail
1326+testWidgetDebug
1327+ :: Bool
1328+ -> WD ()
1329+ -- ^ Webdriver commands to run before the JS runs (i.e. on the statically rendered page)
1330+ -> WD b
1331+ -- ^ Webdriver commands to run after hydration switchover
1332+ -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ())
1333+ -- ^ Widget we are testing
1334+ -> WD b
1335+testWidgetDebug withDebugging beforeJS afterSwitchover =
1336+ testWidgetDebug' withDebugging beforeJS (const afterSwitchover)
1337+
1338+-- | TODO: do something about JSExceptions not causing tests to fail
1339+testWidgetDebug'
1340+ :: Bool
1341+ -> WD a
1342+ -- ^ Webdriver commands to run before the JS runs (i.e. on the statically rendered page)
1343+ -> (a -> WD b)
1344+ -- ^ Webdriver commands to run after hydration switchover
1345+ -> (forall m js. TestWidget js (SpiderTimeline Global) m => m ())
1346+ -- ^ Widget we are testing (contents of body)
1347+ -> WD b
1348+testWidgetDebug' withDebugging beforeJS afterSwitchover bodyWidget = do
1349+ let putStrLnDebug :: MonadIO m => Text -> m ()
1350+ putStrLnDebug m = when withDebugging $ liftIO $ putStrLn $ T.unpack m
1351+ staticApp = do
1352+ el "head" $ pure ()
1353+ el "body" $ do
1354+ bodyWidget
1355+ el "script" $ text $ TE.decodeUtf8 $ LBS.toStrict $ jsaddleJs False
1356+ putStrLnDebug "rendering static"
1357+ ((), html) <- liftIO $ renderStatic staticApp
1358+ putStrLnDebug "rendered static"
1359+ waitBeforeJS <- liftIO newEmptyMVar -- Empty until JS should be run
1360+ waitUntilSwitchover <- liftIO newEmptyMVar -- Empty until switchover
1361+ let entryPoint = do
1362+ putStrLnDebug "taking waitBeforeJS"
1363+ liftIO $ takeMVar waitBeforeJS
1364+ let switchOverAction = do
1365+ putStrLnDebug "switchover syncPoint"
1366+ syncPoint
1367+ putStrLnDebug "putting waitUntilSwitchover"
1368+ liftIO $ putMVar waitUntilSwitchover ()
1369+ putStrLnDebug "put waitUntilSwitchover"
1370+ putStrLnDebug "running mainHydrationWidgetWithSwitchoverAction"
1371+ mainHydrationWidgetWithSwitchoverAction switchOverAction blank bodyWidget
1372+ putStrLnDebug "syncPoint after mainHydrationWidgetWithSwitchoverAction"
1373+ syncPoint
1374+ application <- liftIO $ jsaddleOr defaultConnectionOptions entryPoint $ \_ sendResponse -> do
1375+ putStrLnDebug "sending response"
1376+ r <- sendResponse $ responseLBS status200 [] $ "<!doctype html>\n" <> LBS.fromStrict html
1377+ putStrLnDebug "sent response"
1378+ return r
1379+ waitJSaddle <- liftIO newEmptyMVar
1380+ let settings = foldr ($) Warp.defaultSettings
1381+ [ Warp.setPort $ fromIntegral $ toInteger jsaddlePort
1382+ , Warp.setBeforeMainLoop $ do
1383+ putStrLnDebug "putting waitJSaddle"
1384+ putMVar waitJSaddle ()
1385+ putStrLnDebug "put waitJSaddle"
1386+ ]
1387+ -- hSilence to get rid of ConnectionClosed logs
1388+ silenceIfDebug = if withDebugging then id else hSilence [stderr]
1389+ jsaddleWarp = forkIO $ silenceIfDebug $ Warp.runSettings settings application
1390+ jsaddleTid <- liftIO jsaddleWarp
1391+ putStrLnDebug "taking waitJSaddle"
1392+ liftIO $ takeMVar waitJSaddle
1393+ putStrLnDebug "opening page"
1394+ WD.openPage $ "http://localhost:" <> show jsaddlePort
1395+ putStrLnDebug "running beforeJS"
1396+ a <- beforeJS
1397+ putStrLnDebug "putting waitBeforeJS"
1398+ liftIO $ putMVar waitBeforeJS ()
1399+ putStrLnDebug "taking waitUntilSwitchover"
1400+ liftIO $ takeMVar waitUntilSwitchover
1401+ putStrLnDebug "running afterSwitchover"
1402+ b <- afterSwitchover a
1403+ putStrLnDebug "killing jsaddle thread"
1404+ liftIO $ killThread jsaddleTid
1405+ return b
1406+
1407+data Key2 a where
1408+ Key2_Int :: Int -> Key2 Int
1409+ Key2_Char :: Char -> Key2 Char
1410+
1411+deriveGEq ''Key2
1412+deriveGCompare ''Key2
1413+deriveGShow ''Key2
1414+deriveArgDict ''Key2
1415+
1416+instance ShowTag Key2 Identity where
1417+ showTaggedPrec = \case
1418+ Key2_Int _ -> showsPrec
1419+ Key2_Char _ -> showsPrec
1420+
1421+instance EqTag Key2 Identity where
1422+ eqTagged (Key2_Int _) (Key2_Int _) = (==)
1423+ eqTagged (Key2_Char _) (Key2_Char _) = (==)
reflex-dom/benchmarks/krausest.hs+7−7
@@ -28,15 +28,15 @@ main = do
2828 seed <- randomIO
2929 mainWidgetWithHead' (\_ -> headW, \_ -> bodyW seed)
3030
31−titleW :: MonadWidget t m => m ()
31+titleW :: DomBuilder t m => m ()
3232titleW = text "Reflex-dom keyed"
3333
34−headW :: MonadWidget t m => m ()
34+headW :: DomBuilder t m => m ()
3535headW = do
3636 el "title" titleW
3737 elAttr "link" ("href" =: "/css/currentStyle.css" <> "rel" =: "stylesheet") blank
3838
39−bodyW :: forall t m. MonadWidget t m => Entropy -> m ()
39+bodyW :: forall t m. (MonadFix m, MonadHold t m, DomBuilder t m) => Entropy -> m ()
4040bodyW seed = divClass "main" $ divClass "container" $ mdo
4141 buttonEvents <- divClass "jumbotron" $ divClass "row" $ do
4242 divClass "col-md-6" $ el "h1" titleW
@@ -75,14 +75,14 @@ step (m, t) f = ((m', t'), dt)
7575 (m', dt) = f (m, t)
7676 t' = applyAlways dt t
7777
78−buttonW :: MonadWidget t m => Text -> Text -> a -> m (Event t a)
78+buttonW :: DomBuilder t m => Text -> Text -> a -> m (Event t a)
7979buttonW id txt val = divClass "col-sm-6 smallpad" $ buttonW' ("class" =: "btn btn-primary btn-block" <> "id" =: id)
8080 where
8181 buttonW' attrs = do
8282 (b, _) <- elAttr' "button" attrs $ text txt
8383 pure $ val <$ domEvent Click b
8484
85−tableW :: MonadWidget t m => Event t TableDiff -> m (Event t (IntMap Step))
85+tableW :: (MonadHold t m, DomBuilder t m) => Event t TableDiff -> m (Event t (IntMap Step))
8686tableW diff = do
8787 elClass "table" "table table-hover table-striped test-data" $ do
8888 el "tbody" $ do
@@ -90,7 +90,7 @@ tableW diff = do
9090 rowEvents <- holdIncremental v0 v'
9191 return $ mergeIntIncremental rowEvents
9292
93−rowW :: MonadWidget t m => Row -> m (Event t Step)
93+rowW :: DomBuilder t m => Row -> m (Event t Step)
9494rowW row = elClass "tr" (if selected row then "danger" else "") $
9595 do
9696 elClass "td" "col-md-1" $ do
@@ -102,7 +102,7 @@ rowW row = elClass "tr" (if selected row then "danger" else "") $
102102 where
103103 tagClick f el = f row <$ (domEvent Click el)
104104
105−deleteW :: MonadWidget t m => m ()
105+deleteW :: DomBuilder t m => m ()
106106deleteW = el "a" $ elAttr "span" ("aria-hidden" =: "true" <> "class" =: "glyphicon glyphicon-remove") blank
107107
108108
reflex-dom/src/Reflex/Dom.hs+2−1
@@ -6,5 +6,6 @@ module Reflex.Dom (module X) where
66
77import Foreign.JavaScript.Orphans ()
88import Reflex.Dom.Core as X hiding (mainWidget, mainWidgetInElementById, mainWidgetWithCss,
9− mainWidgetWithHead, mainWidgetWithHead', runApp')
9+ mainWidgetWithHead, mainWidgetWithHead', runApp',
10+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
1011import Reflex.Dom.Internal as X
reflex-dom/src/Reflex/Dom/Internal.hs+18−4
@@ -4,18 +4,24 @@
44{-# LANGUAGE RankNTypes #-}
55{-# LANGUAGE TypeFamilies #-}
66module Reflex.Dom.Internal
7− (module Main, run, mainWidget, mainWidgetWithHead, mainWidgetWithCss,
8− mainWidgetWithHead', mainWidgetInElementById, runApp') where
7+ ( module Main
8+ , run
9+ , mainWidget
10+ , mainWidgetWithHead, mainWidgetWithCss, mainWidgetWithHead', mainWidgetInElementById, runApp'
11+ , mainHydrationWidgetWithHead, mainHydrationWidgetWithHead'
12+ ) where
913
1014import Data.ByteString (ByteString)
1115import Data.Text (Text)
1216import Reflex.Dom.Core (Widget)
1317import Reflex.Dom.Main as Main hiding
1418 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
15− mainWidgetWithHead', mainWidgetInElementById, runApp')
19+ mainWidgetWithHead', mainWidgetInElementById, runApp',
20+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
1621import qualified Reflex.Dom.Main as Main
1722 (mainWidget, mainWidgetWithHead, mainWidgetWithCss,
18− mainWidgetWithHead', mainWidgetInElementById, runApp')
23+ mainWidgetWithHead', mainWidgetInElementById, runApp',
24+ mainHydrationWidgetWithHead, mainHydrationWidgetWithHead')
1925
2026#if defined(ghcjs_HOST_OS)
2127run :: a -> a
@@ -101,3 +107,11 @@ mainWidgetInElementById eid w = run $ Main.mainWidgetInElementById eid w
101107runApp' :: (forall x. AppInput DomTimeline -> Widget x (AppOutput DomTimeline)) -> IO ()
102108runApp' app = run $ Main.runApp' app
103109{-# INLINE runApp' #-}
110+
111+mainHydrationWidgetWithHead :: (forall x. HydrationWidget x ()) -> (forall x. HydrationWidget x ()) -> IO ()
112+mainHydrationWidgetWithHead h b = run $ Main.mainHydrationWidgetWithHead h b
113+{-# INLINE mainHydrationWidgetWithHead #-}
114+
115+mainHydrationWidgetWithHead' :: HydrationWidget () () -> HydrationWidget () () -> IO ()
116+mainHydrationWidgetWithHead' h b = run $ Main.mainHydrationWidgetWithHead' h b
117+{-# INLINE mainHydrationWidgetWithHead' #-}