From 7d31b1727378b4ea615cea5d91535142a2fa70c0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 23 Sep 2014 17:27:00 -0600 Subject: [PATCH] Use the CommandRegistry to register activation event listeners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- spec/package-manager-spec.coffee | 23 +++++++----- src/command-registry.coffee | 24 +++---------- src/package.coffee | 61 +++++++++++--------------------- 3 files changed, 40 insertions(+), 68 deletions(-) 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) ->