diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index bc9db25eb..90bfb4849 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -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", -> diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 73cbdfe22..800b48077 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -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? diff --git a/src/package.coffee b/src/package.coffee index df73bfc5f..953da9e22 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -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) ->