Use the CommandRegistry to register activation event listeners

Commands registered with the command registry will always be handled
first, so as long as we disable any listeners in the registry that were
already invoked for the current command, we don’t need to disable jQuery
methods before replaying the command after activating the package.

This commit adds the ability to call .disableInvokedListeners on the
event passed to the command listeners. This returns a function which
can be called to reenable them.
This commit is contained in:
Nathan Sobo
2014-09-23 17:27:00 -06:00
parent 066f6bf03c
commit 7d31b17273
3 changed files with 40 additions and 68 deletions

View File

@@ -93,7 +93,7 @@ describe "PackageManager", ->
expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2
describe "when the package metadata includes activation events", ->
[mainModule, promise] = []
[mainModule, promise, workspaceCommandListener] = []
beforeEach ->
atom.workspaceView.attachToDom()
@@ -103,6 +103,9 @@ describe "PackageManager", ->
spyOn(mainModule, 'activate').andCallThrough()
spyOn(Package.prototype, 'requireMainModule').andCallThrough()
workspaceCommandListener = jasmine.createSpy('workspaceCommandListener')
atom.commands.add '.workspace', 'activation-event', workspaceCommandListener
promise = atom.packages.activatePackage('package-with-activation-events')
it "defers requiring/activating the main module until an activation event bubbles to the root view", ->
@@ -118,21 +121,23 @@ describe "PackageManager", ->
runs ->
editorView = atom.workspaceView.getActiveView()
eventHandler = jasmine.createSpy("activation event handler")
globalCommandHandler = jasmine.createSpy("activation global command handler")
editorView.command 'activation-event', eventHandler
commandDisposable = atom.commands.add '.workspace', 'activation-event', globalCommandHandler
legacyCommandListener = jasmine.createSpy("legacyCommandListener")
editorView.command 'activation-event', legacyCommandListener
editorCommandListener = jasmine.createSpy("editorCommandListener")
atom.commands.add '.editor', 'activation-event', editorCommandListener
editorView[0].dispatchEvent(new CustomEvent('activation-event', bubbles: true))
expect(mainModule.activate.callCount).toBe 1
expect(mainModule.activationEventCallCount).toBe 1
expect(mainModule.activationCommandCallCount).toBe 1
expect(eventHandler.callCount).toBe 1
expect(globalCommandHandler.callCount).toBe 1
expect(legacyCommandListener.callCount).toBe 1
expect(editorCommandListener.callCount).toBe 1
expect(workspaceCommandListener.callCount).toBe 1
editorView[0].dispatchEvent(new CustomEvent('activation-event', bubbles: true))
expect(mainModule.activationEventCallCount).toBe 2
expect(mainModule.activationCommandCallCount).toBe 2
expect(eventHandler.callCount).toBe 2
expect(globalCommandHandler.callCount).toBe 2
expect(legacyCommandListener.callCount).toBe 2
expect(editorCommandListener.callCount).toBe 2
expect(workspaceCommandListener.callCount).toBe 2
expect(mainModule.activate.callCount).toBe 1
it "activates the package immediately when the events are empty", ->

View File

@@ -137,25 +137,6 @@ class CommandRegistry
commands
disableCommands: (target, commandName) ->
if @rootNode.contains(target)
currentTarget = target
else
currentTarget = @rootNode
disabledListeners = []
loop
for listener in @listenersByCommandName[commandName] ? []
if currentTarget.webkitMatchesSelector(listener.selector)
listener.enabled = false
disabledListeners.push(listener)
break if currentTarget is @rootNode
currentTarget = currentTarget.parentNode
new Disposable =>
listener.enabled = true for listener in disabledListeners
# Public: Simulate the dispatch of a command on a DOM node.
#
# This can be useful for testing when you want to simulate the invocation of a
@@ -184,6 +165,7 @@ class CommandRegistry
immediatePropagationStopped = false
matched = false
currentTarget = originalEvent.target
invokedListeners = []
syntheticEvent = Object.create originalEvent,
eventPhase: value: Event.BUBBLING_PHASE
@@ -195,6 +177,9 @@ class CommandRegistry
originalEvent.stopImmediatePropagation()
propagationStopped = true
immediatePropagationStopped = true
disableInvokedListeners: value: ->
listener.enabled = false for listener in invokedListeners
-> listener.enabled = true for listener in invokedListeners
loop
matchingListeners =
@@ -206,6 +191,7 @@ class CommandRegistry
for listener in matchingListeners when listener.enabled
break if immediatePropagationStopped
invokedListeners.push(listener)
listener.callback.call(currentTarget, syntheticEvent)
break unless currentTarget?

View File

@@ -5,7 +5,7 @@ async = require 'async'
CSON = require 'season'
fs = require 'fs-plus'
EmitterMixin = require('emissary').Emitter
{Emitter} = require 'event-kit'
{Emitter, CompositeDisposable} = require 'event-kit'
Q = require 'q'
{deprecate} = require 'grim'
@@ -270,7 +270,7 @@ class Package
deactivate: ->
@activationDeferred?.reject()
@activationDeferred = null
@unsubscribeFromActivationEvents()
@activationCommandSubscriptions?.dispose()
@deactivateResources()
@deactivateConfig()
if @mainActivated
@@ -333,51 +333,32 @@ class Package
subscribeToActivationEvents: ->
return unless @metadata.activationEvents?
@activationCommandSubscriptions = new CompositeDisposable
if _.isArray(@metadata.activationEvents)
atom.workspaceView.command(event, @handleActivationEvent) for event in @metadata.activationEvents
for eventName in @metadata.activationEvents
@activationCommandSubscriptions.add(
atom.commands.add('.workspace', eventName, @handleActivationEvent)
)
else if _.isString(@metadata.activationEvents)
atom.workspaceView.command(@metadata.activationEvents, @handleActivationEvent)
eventName = @metadata.activationEvents
@activationCommandSubscriptions.add(
atom.commands.add('.workspace', eventName, @handleActivationEvent)
)
else
atom.workspaceView.command(event, selector, @handleActivationEvent) for event, selector of @metadata.activationEvents
for eventName, selector of @metadata.activationEvents
@activationCommandSubscriptions.add(
atom.commands.add(selector, eventName, @handleActivationEvent)
)
handleActivationEvent: (event) =>
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
disabledCommands = atom.commands.disableCommands(event.target, event.type)
event.stopImmediatePropagation()
@activationCommandSubscriptions.dispose()
reenableInvokedListeners = event.disableInvokedListeners()
@activateNow()
event.target.dispatchEvent(new CustomEvent(event.type, bubbles: true))
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
disabledCommands.dispose()
@unsubscribeFromActivationEvents()
event.stopImmediatePropagation()
unsubscribeFromActivationEvents: ->
return unless atom.workspaceView?
if _.isArray(@metadata.activationEvents)
atom.workspaceView.off(event, @handleActivationEvent) for event in @metadata.activationEvents
else if _.isString(@metadata.activationEvents)
atom.workspaceView.off(@metadata.activationEvents, @handleActivationEvent)
else
atom.workspaceView.off(event, selector, @handleActivationEvent) for event, selector of @metadata.activationEvents
disableEventHandlersOnBubblePath: (event) ->
bubblePathEventHandlers = []
disabledHandler = ->
$ ?= require('./space-pen-extensions').$
element = $(event.target)
while element.length
if eventHandlers = element.handlers()?[event.type]
for eventHandler in eventHandlers
eventHandler.disabledHandler = eventHandler.handler
eventHandler.handler = disabledHandler
bubblePathEventHandlers.push(eventHandler)
element = element.parent()
bubblePathEventHandlers
restoreEventHandlersOnBubblePath: (eventHandlers) ->
for eventHandler in eventHandlers
eventHandler.handler = eventHandler.disabledHandler
delete eventHandler.disabledHandler
reenableInvokedListeners()
# Does the given module path contain native code?
isNativeModule: (modulePath) ->