From e323e742ee057aabe5eeacd52592c312c9897363 Mon Sep 17 00:00:00 2001 From: simonihmig Date: Fri, 25 May 2018 13:57:12 +0200 Subject: [PATCH] Deprecate accessing jQuery.Event#originalEvent Implements the deprecation message for user-land code accessing `originalEvent` on `jQuery.Event` instances, as proposed by RFC#294 (https://github.com/emberjs/rfcs/blob/master/text/0294-optional-jquery.md#introducing-ember-jquery-legacy-and-deprecating-jqueryevent-usage) --- .../integration/event-dispatcher-test.js | 161 ++++++++++++++++++ .../lib/system/event_dispatcher.js | 5 +- .../lib/system/jquery_event_deprecation.js | 51 ++++++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 packages/ember-views/lib/system/jquery_event_deprecation.js diff --git a/packages/ember-glimmer/tests/integration/event-dispatcher-test.js b/packages/ember-glimmer/tests/integration/event-dispatcher-test.js index 16ac748b3de..cb2bff0be57 100644 --- a/packages/ember-glimmer/tests/integration/event-dispatcher-test.js +++ b/packages/ember-glimmer/tests/integration/event-dispatcher-test.js @@ -6,6 +6,10 @@ import { reset as instrumentationReset, } from '@ember/instrumentation'; import { EMBER_IMPROVED_INSTRUMENTATION } from '@ember/canary-features'; +import { jQueryDisabled, jQuery } from 'ember-views'; +import { ENV } from 'ember-environment'; +import { HAS_NATIVE_PROXY } from 'ember-utils'; +import { DEBUG } from '@glimmer/env'; let canDataTransfer = !!document.createEvent('HTMLEvents').dataTransfer; @@ -300,3 +304,160 @@ if (canDataTransfer) { } ); } + +if (jQueryDisabled) { + moduleFor( + 'EventDispatcher#native-events', + class extends RenderingTest { + ['@test native events are passed when jQuery is not present'](assert) { + let receivedEvent; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + click(event) { + receivedEvent = event; + }, + }), + template: ``, + }); + + this.render(`{{x-foo}}`); + + this.runTask(() => this.$('#foo').click()); + assert.ok(receivedEvent, 'click event was triggered'); + assert.notOk(receivedEvent.originalEvent, 'event is not a jQuery.Event'); + } + } + ); +} else { + moduleFor( + 'EventDispatcher#jquery-events', + class extends RenderingTest { + beforeEach() { + this.jqueryIntegration = ENV._JQUERY_INTEGRATION; + } + + afterEach() { + ENV._JQUERY_INTEGRATION = this.jqueryIntegration; + } + + ['@test jQuery events are passed when jQuery is present'](assert) { + let receivedEvent; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + click(event) { + receivedEvent = event; + }, + }), + template: ``, + }); + + this.render(`{{x-foo}}`); + + this.runTask(() => this.$('#foo').click()); + assert.ok(receivedEvent, 'click event was triggered'); + assert.ok(receivedEvent instanceof jQuery.Event, 'event is a jQuery.Event'); + } + + [`@${HAS_NATIVE_PROXY ? 'test' : 'skip'} accessing jQuery.Event#originalEvent is deprecated`]( + assert + ) { + let receivedEvent; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + click(event) { + receivedEvent = event; + }, + }), + template: ``, + }); + + this.render(`{{x-foo}}`); + + this.runTask(() => this.$('#foo').click()); + expectDeprecation(() => { + let { originalEvent } = receivedEvent; + assert.ok(originalEvent, 'jQuery event has originalEvent property'); + assert.equal(originalEvent.type, 'click', 'properties of originalEvent are available'); + }, 'Accessing jQuery.Event specific properties is deprecated. Either use the ember-jquery-legacy addon to normalize events to native events, or explicitly opt into jQuery integration using @ember/optional-features.'); + } + + ['@test other jQuery.Event properties do not trigger deprecation'](assert) { + let receivedEvent; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + click(event) { + receivedEvent = event; + }, + }), + template: ``, + }); + + this.render(`{{x-foo}}`); + + this.runTask(() => this.$('#foo').click()); + expectNoDeprecation(() => { + receivedEvent.stopPropagation(); + receivedEvent.stopImmediatePropagation(); + receivedEvent.preventDefault(); + assert.ok(receivedEvent.bubbles, 'properties of jQuery event are available'); + assert.equal(receivedEvent.type, 'click', 'properties of jQuery event are available'); + }); + } + + ['@test accessing jQuery.Event#originalEvent does not trigger deprecations when jquery integration is explicitly enabled']( + assert + ) { + let receivedEvent; + ENV._JQUERY_INTEGRATION = true; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + click(event) { + receivedEvent = event; + }, + }), + template: ``, + }); + + this.render(`{{x-foo}}`); + + this.runTask(() => this.$('#foo').click()); + expectNoDeprecation(() => { + let { originalEvent } = receivedEvent; + assert.ok(originalEvent, 'jQuery event has originalEvent property'); + assert.equal(originalEvent.type, 'click', 'properties of originalEvent are available'); + }); + } + + [`@${ + HAS_NATIVE_PROXY && DEBUG ? 'test' : 'skip' + } accessing jQuery.Event#__originalEvent does not trigger deprecations to support ember-jquery-legacy`]( + assert + ) { + let receivedEvent; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + click(event) { + receivedEvent = event; + }, + }), + template: ``, + }); + + this.render(`{{x-foo}}`); + + this.runTask(() => this.$('#foo').click()); + expectNoDeprecation(() => { + let { __originalEvent: originalEvent } = receivedEvent; + assert.ok(originalEvent, 'jQuery event has __originalEvent property'); + assert.equal(originalEvent.type, 'click', 'properties of __originalEvent are available'); + }); + } + } + ); +} diff --git a/packages/ember-views/lib/system/event_dispatcher.js b/packages/ember-views/lib/system/event_dispatcher.js index 3a557c451d4..acfde773836 100644 --- a/packages/ember-views/lib/system/event_dispatcher.js +++ b/packages/ember-views/lib/system/event_dispatcher.js @@ -10,6 +10,7 @@ import { Object as EmberObject } from 'ember-runtime'; import jQuery from './jquery'; import ActionManager from './action_manager'; import fallbackViewRegistry from '../compat/fallback-view-registry'; +import addJQueryEventDeprecation from './jquery_event_deprecation'; const HAS_JQUERY = jQuery !== undefined; const ROOT_ELEMENT_CLASS = 'ember-application'; @@ -244,7 +245,7 @@ export default EmberObject.extend({ let result = true; if (view) { - result = view.handleEvent(eventName, evt); + result = view.handleEvent(eventName, addJQueryEventDeprecation(evt)); } return result; @@ -254,6 +255,8 @@ export default EmberObject.extend({ let attributes = evt.currentTarget.attributes; let handledActions = []; + evt = addJQueryEventDeprecation(evt); + for (let i = 0; i < attributes.length; i++) { let attr = attributes.item(i); let attrName = attr.name; diff --git a/packages/ember-views/lib/system/jquery_event_deprecation.js b/packages/ember-views/lib/system/jquery_event_deprecation.js new file mode 100644 index 00000000000..3f3acef4662 --- /dev/null +++ b/packages/ember-views/lib/system/jquery_event_deprecation.js @@ -0,0 +1,51 @@ +/* global Proxy */ +import { deprecate } from '@ember/debug'; +import { ENV } from 'ember-environment'; +import { HAS_NATIVE_PROXY } from 'ember-utils'; +import { DEBUG } from '@glimmer/env'; + +export default function addJQueryEventDeprecation(jqEvent) { + if (!DEBUG || !HAS_NATIVE_PROXY) { + return jqEvent; + } + + let boundFunctions = new Map(); + + // wrap the jQuery event in a Proxy to add the deprecation message for originalEvent, according to RFC#294 + // we need a native Proxy here, so we can make sure that the internal use of originalEvent in jQuery itself does + // not trigger a deprecation + return new Proxy(jqEvent, { + get(target, name) { + switch (name) { + case 'originalEvent': + deprecate( + 'Accessing jQuery.Event specific properties is deprecated. Either use the ember-jquery-legacy addon to normalize events to native events, or explicitly opt into jQuery integration using @ember/optional-features.', + ENV._JQUERY_INTEGRATION === true, + { + id: 'ember-views.event-dispatcher.jquery-event', + until: '4.0.0', + } + ); + return target[name]; + + // provide an escape hatch for ember-jquery-legacy to access originalEvent without a deprecation + case '__originalEvent': + return target.originalEvent; + + default: + if (typeof target[name] === 'function') { + // cache functions for reuse + if (!boundFunctions.has(name)) { + // for jQuery.Event methods call them with `target` as the `this` context, so they will access + // `originalEvent` from the original jQuery event, not our proxy, thus not trigger the deprecation + boundFunctions.set(name, target[name].bind(target)); + } + + return boundFunctions.get(name); + } + // same for jQuery's getter functions for simple properties + return target[name]; + } + }, + }); +}