From f2b428b4fe1eecd66ee95513da779470f7c621aa Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 31 Dec 2014 19:04:26 +0000 Subject: Modes proof-of-concept. --- lib/handler_stack.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 858f2ec9..728ea4bc 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -5,6 +5,7 @@ class root.HandlerStack constructor: -> @stack = [] @counter = 0 + @passThrough = {} genId: -> @counter = ++@counter & 0xffff @@ -18,6 +19,7 @@ class root.HandlerStack # propagation by returning a falsy value. bubbleEvent: (type, event) -> for i in [(@stack.length - 1)..0] by -1 + console.log i, type handler = @stack[i] # We need to check for existence of handler because the last function call may have caused the release # of more than one handler. @@ -27,6 +29,10 @@ class root.HandlerStack if not passThrough DomUtils.suppressEvent(event) return false + # If @passThrough is returned, then discontinue further bubbling and pass the event through to the + # underlying page. The event is not suppresssed. + if passThrough == @passThrough + return false true remove: (id = @currentId) -> @@ -35,3 +41,5 @@ class root.HandlerStack if handler.id == id @stack.splice(i, 1) break + +root.handlerStack = new HandlerStack -- cgit v1.2.3 From acefe43cef5a216cb2504e85799699c359b6b4d8 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 31 Dec 2014 20:52:27 +0000 Subject: Modes; incorporate three test modes. As a proof of concept, this incorporates normal mode, passkeys mode and insert mode. --- lib/handler_stack.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 728ea4bc..1c334210 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -1,11 +1,11 @@ root = exports ? window -class root.HandlerStack +class HandlerStack constructor: -> @stack = [] @counter = 0 - @passThrough = {} + @passThrough = new Object() # Used only as a constant, distinct from any other value. genId: -> @counter = ++@counter & 0xffff @@ -19,7 +19,6 @@ class root.HandlerStack # propagation by returning a falsy value. bubbleEvent: (type, event) -> for i in [(@stack.length - 1)..0] by -1 - console.log i, type handler = @stack[i] # We need to check for existence of handler because the last function call may have caused the release # of more than one handler. @@ -29,8 +28,8 @@ class root.HandlerStack if not passThrough DomUtils.suppressEvent(event) return false - # If @passThrough is returned, then discontinue further bubbling and pass the event through to the - # underlying page. The event is not suppresssed. + # If the constant @passThrough is returned, then discontinue further bubbling and pass the event + # through to the underlying page. The event is not suppresssed. if passThrough == @passThrough return false true @@ -42,4 +41,5 @@ class root.HandlerStack @stack.splice(i, 1) break +root. HandlerStack = HandlerStack root.handlerStack = new HandlerStack -- cgit v1.2.3 From aed5e2b5e1015a2e581edadbc5dd2d1b5a2719f4 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 1 Jan 2015 09:57:06 +0000 Subject: Modes; minor changes. --- lib/handler_stack.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 1c334210..886a9ece 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -41,5 +41,5 @@ class HandlerStack @stack.splice(i, 1) break -root. HandlerStack = HandlerStack +root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack -- cgit v1.2.3 From 2d047e7ee7e77a02ccb29658ada953a092cee20a Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 1 Jan 2015 12:02:16 +0000 Subject: Modes; implement insert mode. --- lib/handler_stack.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 886a9ece..6f599dc7 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -7,7 +7,7 @@ class HandlerStack @counter = 0 @passThrough = new Object() # Used only as a constant, distinct from any other value. - genId: -> @counter = ++@counter & 0xffff + genId: -> @counter = ++@counter # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later. push: (handler) -> -- cgit v1.2.3 From b5535bc5a1b44c12cff62bac601a8d6ec7e04a6c Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 2 Jan 2015 08:23:12 +0000 Subject: Modes; better name for handlerStack.passDirectlyToPage. --- lib/handler_stack.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 6f599dc7..8929fa53 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -5,7 +5,7 @@ class HandlerStack constructor: -> @stack = [] @counter = 0 - @passThrough = new Object() # Used only as a constant, distinct from any other value. + @passDirectlyToPage = new Object() # Used only as a constant, distinct from any other value. genId: -> @counter = ++@counter @@ -28,9 +28,9 @@ class HandlerStack if not passThrough DomUtils.suppressEvent(event) return false - # If the constant @passThrough is returned, then discontinue further bubbling and pass the event - # through to the underlying page. The event is not suppresssed. - if passThrough == @passThrough + # If the constant @passDirectlyToPage is returned, then discontinue further bubbling and pass the + # event through to the underlying page. The event is not suppresssed. + if passThrough == @passDirectlyToPage return false true -- cgit v1.2.3 From 20ebbf3de2384738af916a441470d74a5aca14a3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 2 Jan 2015 10:24:39 +0000 Subject: Modes; rework badge handling and fix passkeys mode. --- lib/handler_stack.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 8929fa53..8de6ec12 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -26,7 +26,7 @@ class HandlerStack @currentId = handler.id passThrough = handler[type].call(@, event) if not passThrough - DomUtils.suppressEvent(event) + DomUtils.suppressEvent(event) if @isChromeEvent event return false # If the constant @passDirectlyToPage is returned, then discontinue further bubbling and pass the # event through to the underlying page. The event is not suppresssed. @@ -41,5 +41,10 @@ class HandlerStack @stack.splice(i, 1) break + # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events. + # This checks whether that the event at hand is a chrome event. + isChromeEvent: (event) -> + event?.preventDefault? and event?.stopImmediatePropagation? + root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack -- cgit v1.2.3 From 298ee34b1c90b0203a74a2d158858428475bfd95 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 2 Jan 2015 11:47:42 +0000 Subject: Modes; fix insert mode. --- lib/handler_stack.coffee | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 8de6ec12..f56683f1 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -46,5 +46,10 @@ class HandlerStack isChromeEvent: (event) -> event?.preventDefault? and event?.stopImmediatePropagation? + # Convenience wrapper for handlers which always continue propagation. + alwaysPropagate: (handler) -> + handler() + true + root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack -- cgit v1.2.3 From 2d8c478e8086abf80b206d0fd8abc488a035b5cd Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 2 Jan 2015 15:59:58 +0000 Subject: Modes; incorporate find mode. --- lib/handler_stack.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index f56683f1..764461e7 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -6,6 +6,7 @@ class HandlerStack @stack = [] @counter = 0 @passDirectlyToPage = new Object() # Used only as a constant, distinct from any other value. + @eventConsumed = new Object() # Used only as a constant, distinct from any other value. genId: -> @counter = ++@counter @@ -32,6 +33,10 @@ class HandlerStack # event through to the underlying page. The event is not suppresssed. if passThrough == @passDirectlyToPage return false + # If the constant @eventConsumed is returned, then discontinue further bubbling and + # return false. + if passThrough == @eventConsumed + return false true remove: (id = @currentId) -> @@ -51,5 +56,10 @@ class HandlerStack handler() true + # Convenience wrapper for handlers which never continue propagation. + neverPropagate: (handler) -> + handler() + false + root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack -- cgit v1.2.3 From 072bb424d16e6faba243dcf1ab247494cbf8c9ee Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 2 Jan 2015 17:57:19 +0000 Subject: Modes; better constant naming. --- lib/handler_stack.coffee | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 764461e7..d2d0672a 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -5,8 +5,14 @@ class HandlerStack constructor: -> @stack = [] @counter = 0 - @passDirectlyToPage = new Object() # Used only as a constant, distinct from any other value. - @eventConsumed = new Object() # Used only as a constant, distinct from any other value. + + # A handler should return this value to immediately discontinue bubbling and pass the event on to the + # underlying page. + @stopBubblingAndTrue = new Object() + + # A handler should return this value to indicate that the event has been consumed, and no further + # processing should take place. + @stopBubblingAndFalse = new Object() genId: -> @counter = ++@counter @@ -29,14 +35,8 @@ class HandlerStack if not passThrough DomUtils.suppressEvent(event) if @isChromeEvent event return false - # If the constant @passDirectlyToPage is returned, then discontinue further bubbling and pass the - # event through to the underlying page. The event is not suppresssed. - if passThrough == @passDirectlyToPage - return false - # If the constant @eventConsumed is returned, then discontinue further bubbling and - # return false. - if passThrough == @eventConsumed - return false + return true if passThrough == @stopBubblingAndTrue + return false if passThrough == @stopBubblingAndFalse true remove: (id = @currentId) -> -- cgit v1.2.3 From 103fcde7c51fe83bc9c58fc28c3c11ce6a791f0f Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 3 Jan 2015 05:58:09 +0000 Subject: Modes; more renaming and refactoring. --- lib/handler_stack.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index d2d0672a..f05413d3 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -51,13 +51,12 @@ class HandlerStack isChromeEvent: (event) -> event?.preventDefault? and event?.stopImmediatePropagation? - # Convenience wrapper for handlers which always continue propagation. - alwaysPropagate: (handler) -> + # Convenience wrappers. + alwaysContinueBubbling: (handler) -> handler() true - # Convenience wrapper for handlers which never continue propagation. - neverPropagate: (handler) -> + neverContinueBubbling: (handler) -> handler() false -- cgit v1.2.3 From 7e4fdac07ffb59c438a17c2c88051064aaab16b5 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 4 Jan 2015 14:54:04 +0000 Subject: Revise handler stack implementation. The old implementation: - Wasn't actually checking whether handlers had been removed before calling them. - Could end up calling the same handler twice (if a handler was removed further down the stack, and the stack elements moved due the resulting splice. Solution: - Mark elements as removed and check. Set their ids to null. - Don't splice stack. Also, optimisation: - Removing the element at the top of the stack is still O(1). - In Modes, reverse handlers before removing (so, more likely to hit the optimisation above). For the record, the stable stack length at the moment seems to be about 10-12 elements. --- lib/handler_stack.coffee | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index f05413d3..c8e9a035 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -14,24 +14,24 @@ class HandlerStack # processing should take place. @stopBubblingAndFalse = new Object() - genId: -> @counter = ++@counter - # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later. push: (handler) -> - handler.id = @genId() @stack.push handler - handler.id + handler.id = ++@counter - # Called whenever we receive a key event. Each individual handler has the option to stop the event's - # propagation by returning a falsy value. + # Called whenever we receive a key or other event. Each individual handler has the option to stop the + # event's propagation by returning a falsy value, or stop bubbling by returning @stopBubblingAndFalse or + # @stopBubblingAndTrue. bubbleEvent: (type, event) -> + # extra is passed to each handler. This allows handlers to pass information down the stack. + extra = {} for i in [(@stack.length - 1)..0] by -1 handler = @stack[i] # We need to check for existence of handler because the last function call may have caused the release # of more than one handler. - if handler && handler[type] + if handler and handler.id and handler[type] @currentId = handler.id - passThrough = handler[type].call(@, event) + passThrough = handler[type].call @, event, extra if not passThrough DomUtils.suppressEvent(event) if @isChromeEvent event return false @@ -40,11 +40,17 @@ class HandlerStack true remove: (id = @currentId) -> - for i in [(@stack.length - 1)..0] by -1 - handler = @stack[i] - if handler.id == id - @stack.splice(i, 1) - break + if 0 < @stack.length and @stack[@stack.length-1].id == id + # A common case is to remove the handler at the top of the stack. And we can this very efficiently. + # Tests suggest that this case arises more than half of the time. + @stack.pop().id = null + else + # Otherwise, we'll build a new stack. This is better than splicing the existing stack since at can't + # interfere with any concurrent bubbleEvent. + @stack = @stack.filter (handler) -> + # Mark this handler as removed (for any active bubbleEvent call). + handler.id = null if handler.id == id + handler?.id? # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events. # This checks whether that the event at hand is a chrome event. -- cgit v1.2.3 From 45b2674e461659327f8e41ba10035abddde29b26 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 4 Jan 2015 15:26:32 +0000 Subject: Modes; comment tweeks. --- lib/handler_stack.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index c8e9a035..17e4844b 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -41,12 +41,12 @@ class HandlerStack remove: (id = @currentId) -> if 0 < @stack.length and @stack[@stack.length-1].id == id - # A common case is to remove the handler at the top of the stack. And we can this very efficiently. + # A common case is to remove the handler at the top of the stack. And we can do this very efficiently. # Tests suggest that this case arises more than half of the time. @stack.pop().id = null else - # Otherwise, we'll build a new stack. This is better than splicing the existing stack since at can't - # interfere with any concurrent bubbleEvent. + # Otherwise, we'll build a new stack. This is better than splicing the existing stack since that can + # interfere with concurrent bubbleEvents. @stack = @stack.filter (handler) -> # Mark this handler as removed (for any active bubbleEvent call). handler.id = null if handler.id == id -- cgit v1.2.3 From 73f66f25e6b8e5b5b8456074ad4fa79ba1d3ca4d Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 4 Jan 2015 16:19:14 +0000 Subject: Modes; revise InsertMode as two classes. --- lib/handler_stack.coffee | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 17e4844b..0a34087f 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -40,17 +40,12 @@ class HandlerStack true remove: (id = @currentId) -> - if 0 < @stack.length and @stack[@stack.length-1].id == id - # A common case is to remove the handler at the top of the stack. And we can do this very efficiently. - # Tests suggest that this case arises more than half of the time. - @stack.pop().id = null - else - # Otherwise, we'll build a new stack. This is better than splicing the existing stack since that can - # interfere with concurrent bubbleEvents. - @stack = @stack.filter (handler) -> - # Mark this handler as removed (for any active bubbleEvent call). - handler.id = null if handler.id == id - handler?.id? + # This is more expense than splicing @stack, but better because splicing can interfere with concurrent + # bubbleEvents. + @stack = @stack.filter (handler) -> + # Mark this handler as removed (to notify any concurrent bubbleEvent call). + if handler.id == id then handler.id = null + handler?.id? # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events. # This checks whether that the event at hand is a chrome event. -- cgit v1.2.3 From 04ac4c64c9634d9f81035ff7e9db537f39b42f3c Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 7 Jan 2015 09:57:03 +0000 Subject: Modes; rework Singletons, InsertModeBlocker and HandlerStack. This begins work on addressing @philc's comments in #1413. That work is nevertheless not yet complete. --- lib/handler_stack.coffee | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 0a34087f..9da0bc33 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -15,8 +15,10 @@ class HandlerStack @stopBubblingAndFalse = new Object() # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later. + # We use unshift (which is more expensive than push) so that bubbleEvent can just iterate over the stack in + # the normal order. push: (handler) -> - @stack.push handler + @stack.unshift handler handler.id = ++@counter # Called whenever we receive a key or other event. Each individual handler has the option to stop the @@ -25,26 +27,28 @@ class HandlerStack bubbleEvent: (type, event) -> # extra is passed to each handler. This allows handlers to pass information down the stack. extra = {} - for i in [(@stack.length - 1)..0] by -1 - handler = @stack[i] - # We need to check for existence of handler because the last function call may have caused the release - # of more than one handler. - if handler and handler.id and handler[type] + for handler in @stack[..] # Take a copy of @stack, so that concurrent removes do not interfere. + # We need to check whether the handler has been removed (handler.id == null). + if handler and handler.id @currentId = handler.id - passThrough = handler[type].call @, event, extra - if not passThrough - DomUtils.suppressEvent(event) if @isChromeEvent event - return false - return true if passThrough == @stopBubblingAndTrue - return false if passThrough == @stopBubblingAndFalse + # A handler can register a handler for type "all", which will be invoked on all events. Such an "all" + # handler will be invoked first. + for func in [ handler.all, handler[type] ] + if func + passThrough = func.call @, event, extra + if not passThrough + DomUtils.suppressEvent(event) if @isChromeEvent event + return false + return true if passThrough == @stopBubblingAndTrue + return false if passThrough == @stopBubblingAndFalse true remove: (id = @currentId) -> # This is more expense than splicing @stack, but better because splicing can interfere with concurrent # bubbleEvents. @stack = @stack.filter (handler) -> - # Mark this handler as removed (to notify any concurrent bubbleEvent call). - if handler.id == id then handler.id = null + # Mark this handler as removed (so concurrent bubbleEvents will know not to invoke it). + handler.id = null if handler.id == id handler?.id? # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events. -- cgit v1.2.3 From 0429da577097bd7d30d12901fcc74385e44d83f4 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 7 Jan 2015 11:29:24 +0000 Subject: Modes; Continue incorporation of comments in #1413. - Slight rework of HandlerStack. - Remove classs ExitOnEscape and ExitOnBlur - Rework InsertMode, plus trigger and blocker. - Remove StateMode. - Do no mixin options. - Lots of tidy up (including set a debug variable to Mode). --- lib/handler_stack.coffee | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 9da0bc33..4d186341 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -15,11 +15,10 @@ class HandlerStack @stopBubblingAndFalse = new Object() # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later. - # We use unshift (which is more expensive than push) so that bubbleEvent can just iterate over the stack in - # the normal order. push: (handler) -> - @stack.unshift handler handler.id = ++@counter + @stack.push handler + handler.id # Called whenever we receive a key or other event. Each individual handler has the option to stop the # event's propagation by returning a falsy value, or stop bubbling by returning @stopBubblingAndFalse or @@ -27,8 +26,10 @@ class HandlerStack bubbleEvent: (type, event) -> # extra is passed to each handler. This allows handlers to pass information down the stack. extra = {} - for handler in @stack[..] # Take a copy of @stack, so that concurrent removes do not interfere. - # We need to check whether the handler has been removed (handler.id == null). + # We take a copy of the array, here, in order to avoid interference from concurrent removes (for example, + # to avoid calling the same handler twice). + for handler in @stack[..].reverse() + # A handler may have been removed (handler.id == null). if handler and handler.id @currentId = handler.id # A handler can register a handler for type "all", which will be invoked on all events. Such an "all" @@ -44,12 +45,12 @@ class HandlerStack true remove: (id = @currentId) -> - # This is more expense than splicing @stack, but better because splicing can interfere with concurrent - # bubbleEvents. - @stack = @stack.filter (handler) -> - # Mark this handler as removed (so concurrent bubbleEvents will know not to invoke it). - handler.id = null if handler.id == id - handler?.id? + for i in [(@stack.length - 1)..0] by -1 + handler = @stack[i] + if handler.id == id + handler.id = null + @stack.splice(i, 1) + break # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events. # This checks whether that the event at hand is a chrome event. -- cgit v1.2.3 From 7c886d32cca6c0540a9ec6247eb1617b8f1db86a Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 8 Jan 2015 07:20:55 +0000 Subject: Modes; refactor PostFindMode key handling. In particular, this refactors the handling of non-vimium key events in PostFindMode. This implements option 2 from #1415. However, #1415 is not resolved, and option 3 remains a viable option. --- lib/handler_stack.coffee | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 4d186341..718bee9d 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -14,11 +14,17 @@ class HandlerStack # processing should take place. @stopBubblingAndFalse = new Object() - # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later. + # Adds a handler to the top of the stack. Returns a unique ID for that handler that can be used to remove it + # later. push: (handler) -> - handler.id = ++@counter @stack.push handler - handler.id + handler.id = ++@counter + + # Adds a handler to the bottom of the stack. Returns a unique ID for that handler that can be used to remove + # it later. + unshift: (handler) -> + @stack.unshift handler + handler.id = ++@counter # Called whenever we receive a key or other event. Each individual handler has the option to stop the # event's propagation by returning a falsy value, or stop bubbling by returning @stopBubblingAndFalse or -- cgit v1.2.3 From 2fe40dd69bb93b620da60464b9cb57c36adaeca1 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 8 Jan 2015 12:02:03 +0000 Subject: Modes; incorporate small changes from #1413. Slightly more significant: Move several utilities to dome_utils. --- lib/handler_stack.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 718bee9d..97e189c5 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -58,10 +58,10 @@ class HandlerStack @stack.splice(i, 1) break - # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events. - # This checks whether that the event at hand is a chrome event. + # The handler stack handles chrome events (which may need to be suppressed) and internal (pseudo) events. + # This checks whether the event at hand is a chrome event. isChromeEvent: (event) -> - event?.preventDefault? and event?.stopImmediatePropagation? + event?.preventDefault? or event?.stopImmediatePropagation? # Convenience wrappers. alwaysContinueBubbling: (handler) -> -- cgit v1.2.3 From ac90db47aa2671cd663cc6a9cdf783dc30a582e9 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 10 Jan 2015 00:00:11 +0000 Subject: Modes; more changes... - Better comments. - Strip unnecessary handlers for leaving post-find mode. - Simplify passKeys. - focusInput now re-bubbles its triggering keydown event. --- lib/handler_stack.coffee | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 97e189c5..44c7538b 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -14,6 +14,11 @@ class HandlerStack # processing should take place. @stopBubblingAndFalse = new Object() + # A handler should return this value to indicate that bubbling should be restarted. Typically, this is + # used when, while bubbling an event, a new mode is pushed onto the stack. See `focusInput` for an + # example. + @restartBubbling = new Object() + # Adds a handler to the top of the stack. Returns a unique ID for that handler that can be used to remove it # later. push: (handler) -> @@ -30,30 +35,26 @@ class HandlerStack # event's propagation by returning a falsy value, or stop bubbling by returning @stopBubblingAndFalse or # @stopBubblingAndTrue. bubbleEvent: (type, event) -> - # extra is passed to each handler. This allows handlers to pass information down the stack. - extra = {} - # We take a copy of the array, here, in order to avoid interference from concurrent removes (for example, - # to avoid calling the same handler twice). + # We take a copy of the array in order to avoid interference from concurrent removes (for example, to + # avoid calling the same handler twice, because elements have been spliced out of the array by remove). for handler in @stack[..].reverse() - # A handler may have been removed (handler.id == null). - if handler and handler.id + # A handler may have been removed (handler.id == null), so check. + if handler?.id and handler[type] @currentId = handler.id - # A handler can register a handler for type "all", which will be invoked on all events. Such an "all" - # handler will be invoked first. - for func in [ handler.all, handler[type] ] - if func - passThrough = func.call @, event, extra - if not passThrough - DomUtils.suppressEvent(event) if @isChromeEvent event - return false - return true if passThrough == @stopBubblingAndTrue - return false if passThrough == @stopBubblingAndFalse + result = handler[type].call @, event + if not result + DomUtils.suppressEvent(event) if @isChromeEvent event + return false + return true if result == @stopBubblingAndTrue + return false if result == @stopBubblingAndFalse + return @bubbleEvent type, event if result == @restartBubbling true remove: (id = @currentId) -> for i in [(@stack.length - 1)..0] by -1 handler = @stack[i] if handler.id == id + # Mark the handler as removed. handler.id = null @stack.splice(i, 1) break @@ -63,7 +64,9 @@ class HandlerStack isChromeEvent: (event) -> event?.preventDefault? or event?.stopImmediatePropagation? - # Convenience wrappers. + # Convenience wrappers. Handlers must return an approriate value. These are wrappers which handlers can + # use to always return the same value. This then means that the handler itself can be implemented without + # regard to its return value. alwaysContinueBubbling: (handler) -> handler() true -- cgit v1.2.3 From fdcdd0113049042c94b2b56a6b716e2da58b860e Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 10 Jan 2015 08:25:02 +0000 Subject: Modes; instrument for debugging... - Set Mode.debug to true to see mode activation/deactivation on the console. - Use Mode.log() to see a list of currently-active modes. - Use handlerStack.debugOn() to enable debugging of the handler stack. --- lib/handler_stack.coffee | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 44c7538b..22d04941 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -3,6 +3,8 @@ root = exports ? window class HandlerStack constructor: -> + @debug = false + @eventNumber = 0 @stack = [] @counter = 0 @@ -22,8 +24,10 @@ class HandlerStack # Adds a handler to the top of the stack. Returns a unique ID for that handler that can be used to remove it # later. push: (handler) -> - @stack.push handler handler.id = ++@counter + handler._name ||= "anon-#{@counter}" + @stack.push handler + handler.id # Adds a handler to the bottom of the stack. Returns a unique ID for that handler that can be used to remove # it later. @@ -35,6 +39,7 @@ class HandlerStack # event's propagation by returning a falsy value, or stop bubbling by returning @stopBubblingAndFalse or # @stopBubblingAndTrue. bubbleEvent: (type, event) -> + @eventNumber += 1 # We take a copy of the array in order to avoid interference from concurrent removes (for example, to # avoid calling the same handler twice, because elements have been spliced out of the array by remove). for handler in @stack[..].reverse() @@ -42,6 +47,7 @@ class HandlerStack if handler?.id and handler[type] @currentId = handler.id result = handler[type].call @, event + @logResult type, event, handler, result if @debug if not result DomUtils.suppressEvent(event) if @isChromeEvent event return false @@ -75,5 +81,31 @@ class HandlerStack handler() false + # Debugging. + debugOn: -> @debug = true + debugOff: -> @debug = false + + logResult: (type, event, handler, result) -> + # FIXME(smblott). Badge updating is too noisy, so we filter it out. However, we do need to look at how + # many badge update events are happening. It seems to be more than necessary. + return if type == "updateBadge" + label = + switch result + when @stopBubblingAndTrue then "stop/true" + when @stopBubblingAndFalse then "stop/false" + when @restartBubbling then "rebubble" + when true then "continue" + label ||= if result then "continue/truthy" else "suppress" + @log @eventNumber, type, handler._name, label + + logRecords: [] + log: (args...) -> + line = args.join " " + @logRecords.push line + console.log line + + clipLog: -> + Clipboard.copy logRecords.join "\n" + root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack -- cgit v1.2.3 From d65075a3b66fae93a10b849162fa907d0eb99846 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 11 Jan 2015 07:15:06 +0000 Subject: Modes; add DOM tests. --- lib/handler_stack.coffee | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 22d04941..c21ba8a8 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -98,14 +98,17 @@ class HandlerStack label ||= if result then "continue/truthy" else "suppress" @log @eventNumber, type, handler._name, label - logRecords: [] log: (args...) -> line = args.join " " - @logRecords.push line console.log line - clipLog: -> - Clipboard.copy logRecords.join "\n" + # Used by tests to get a duplicate copy of the initialized handler stack. + duplicate: -> + dup = new HandlerStack() + dup.stack = @stack[..] + for prop in [ "stopBubblingAndTrue", "stopBubblingAndFalse", "restartBubbling" ] + dup[prop] = @[prop] + dup root.HandlerStack = HandlerStack -root.handlerStack = new HandlerStack +root.handlerStack = new HandlerStack() -- cgit v1.2.3 From e8f10007f1528808f72be6fac829cc55309527f2 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 11 Jan 2015 07:15:20 +0000 Subject: Modes; add DOM tests. --- lib/handler_stack.coffee | 8 -------- 1 file changed, 8 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index c21ba8a8..b4cacf74 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -102,13 +102,5 @@ class HandlerStack line = args.join " " console.log line - # Used by tests to get a duplicate copy of the initialized handler stack. - duplicate: -> - dup = new HandlerStack() - dup.stack = @stack[..] - for prop in [ "stopBubblingAndTrue", "stopBubblingAndFalse", "restartBubbling" ] - dup[prop] = @[prop] - dup - root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack() -- cgit v1.2.3 From 355bb5fb2a06b4465a354350e2fa78ab5d53cb0b Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 11 Jan 2015 10:20:54 +0000 Subject: Modes; rework debugging support. --- lib/handler_stack.coffee | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index b4cacf74..d671fb3a 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -82,9 +82,6 @@ class HandlerStack false # Debugging. - debugOn: -> @debug = true - debugOff: -> @debug = false - logResult: (type, event, handler, result) -> # FIXME(smblott). Badge updating is too noisy, so we filter it out. However, we do need to look at how # many badge update events are happening. It seems to be more than necessary. @@ -96,11 +93,7 @@ class HandlerStack when @restartBubbling then "rebubble" when true then "continue" label ||= if result then "continue/truthy" else "suppress" - @log @eventNumber, type, handler._name, label - - log: (args...) -> - line = args.join " " - console.log line + console.log "#{@eventNumber}", type, handler._name, label root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack() -- cgit v1.2.3 From 8066a3838ef44b010f6dfb46cea8b47c6bdfc087 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 11 Jan 2015 15:01:12 +0000 Subject: Modes; yet more tweaks, yet more tests. --- lib/handler_stack.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index d671fb3a..fddc45db 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -49,7 +49,7 @@ class HandlerStack result = handler[type].call @, event @logResult type, event, handler, result if @debug if not result - DomUtils.suppressEvent(event) if @isChromeEvent event + DomUtils.suppressEvent event if @isChromeEvent event return false return true if result == @stopBubblingAndTrue return false if result == @stopBubblingAndFalse -- cgit v1.2.3 From 0e59b99e95e6a4fd3f64fd284e7417ba5f7e22e1 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 18 Jan 2015 06:27:38 +0000 Subject: Modes; pre-merge clean up. --- lib/handler_stack.coffee | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'lib/handler_stack.coffee') diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index fddc45db..76d835b7 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -13,25 +13,24 @@ class HandlerStack @stopBubblingAndTrue = new Object() # A handler should return this value to indicate that the event has been consumed, and no further - # processing should take place. + # processing should take place. The event does not propagate to the underlying page. @stopBubblingAndFalse = new Object() # A handler should return this value to indicate that bubbling should be restarted. Typically, this is - # used when, while bubbling an event, a new mode is pushed onto the stack. See `focusInput` for an - # example. + # used when, while bubbling an event, a new mode is pushed onto the stack. @restartBubbling = new Object() # Adds a handler to the top of the stack. Returns a unique ID for that handler that can be used to remove it # later. push: (handler) -> - handler.id = ++@counter handler._name ||= "anon-#{@counter}" @stack.push handler - handler.id + handler.id = ++@counter - # Adds a handler to the bottom of the stack. Returns a unique ID for that handler that can be used to remove - # it later. + # As above, except the new handler is added to the bottom of the stack. unshift: (handler) -> + handler._name ||= "anon-#{@counter}" + handler._name += "/unshift" @stack.unshift handler handler.id = ++@counter @@ -84,8 +83,9 @@ class HandlerStack # Debugging. logResult: (type, event, handler, result) -> # FIXME(smblott). Badge updating is too noisy, so we filter it out. However, we do need to look at how - # many badge update events are happening. It seems to be more than necessary. - return if type == "updateBadge" + # many badge update events are happening. It seems to be more than necessary. We also filter out + # registerKeyQueue as unnecessarily noisy and not particularly helpful. + return if type in [ "updateBadge", "registerKeyQueue" ] label = switch result when @stopBubblingAndTrue then "stop/true" -- cgit v1.2.3