Support composed flag

This commit is contained in:
Daniel Freedman
2016-08-11 16:05:03 -07:00
parent 372b667de8
commit 284fea0ec9
3 changed files with 269 additions and 10 deletions

View File

@@ -741,10 +741,67 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_activeElement: activeElementDescriptor
});
// https://github.com/w3c/webcomponents/issues/513#issuecomment-224183937
var alwaysComposed = {
blur: true,
focus: true,
focusin: true,
focusout: true,
click: true,
dblclick: true,
mousedown: true,
mouseenter: true,
mouseleave: true,
mousemove: true,
mouseout: true,
mouseover: true,
mouseup: true,
wheel: true,
beforeinput: true,
input: true,
keydown: true,
keyup: true,
compositionstart: true,
compositionupdate: true,
compositionend: true,
touchstart: true,
touchend: true,
touchmove: true,
touchcancel: true,
pointerover: true,
pointerenter: true,
pointerdown: true,
pointermove: true,
pointerup: true,
pointercancel: true,
pointerout: true,
pointerleave: true,
gotpointercapture: true,
lostpointercapture: true,
dragstart: true,
drag: true,
dragenter: true,
dragleave: true,
dragover: true,
drop: true,
dragend: true,
DOMActivate: true,
DOMFocusIn: true,
DOMFocusOut: true,
keypress: true
};
var EventMixin = {
__patched: 'Event',
get composed() {
if (this.isTrusted && this.__composed === undefined) {
this.__composed = alwaysComposed[this.type] || false;
}
return this.__composed || false;
},
composedPath() {
if (!this.__composedPath) {
var composedPath = [];
@@ -760,12 +817,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
current = contents[contents.length - 1];
} else if (current.assignedSlot) {
current = current.assignedSlot;
} else if (current.host && this.composed) {
current = current.host;
} else {
current = current.parentNode || current.host;
current = current.parentNode;
}
}
// event composedPath includes window in most recent native implementations
composedPath.push(window);
// event composedPath includes window when composed = true in most recent native implementations
if (composedPath[composedPath.length - 1] === document) {
composedPath.push(window);
}
this.__composedPath = composedPath;
}
return this.__composedPath;
@@ -788,7 +849,26 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
ShadyDom.addEventListener = function(type, fn, capture) {
function mixinComposedFlag(Base) {
var klazz = class extends Base {
constructor(type, options) {
super(type, options);
if (options && options.composed) {
this.__composed = true;
} else {
this.__composed = false;
}
}
}
return klazz;
}
ShadyDom.origEvent = Event;
ShadyDom.PatchedEvent = mixinComposedFlag(Event);
ShadyDom.PatchedCustomEvent = mixinComposedFlag(CustomEvent);
ShadyDom.addEventListener = function(type, fn, optionsOrCapture) {
if (!this.__eventListenerCount) {
this.__eventListenerCount = 0;
}
@@ -796,20 +876,25 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var wrappedFn = function(e) {
if (!e.__target) {
e.__target = e.target;
e.__relatedTarget = e.relatedTarget;
var proto = ShadyDom.patchImpl.prototypeForObject(e);
if (proto) {
e.__proto__ = proto;
}
}
return fn(e);
if (e.composedPath().indexOf(this) > -1) {
return fn(e);
} else {
e.stopImmediatePropagation();
}
}
fn.__eventWrapper = wrappedFn;
return origAddEventListener.call(this, type, wrappedFn, capture);
return origAddEventListener.call(this, type, wrappedFn, optionsOrCapture);
};
ShadyDom.removeEventListener = function(type, fn, capture) {
ShadyDom.removeEventListener = function(type, fn, optionsOrCapture) {
var wrapper = fn.__eventWrapper;
origRemoveEventListener.call(this, type, wrapper || fn, capture);
origRemoveEventListener.call(this, type, wrapper || fn, optionsOrCapture);
if (wrapper) {
fn.__eventWrapper = null;
this.__eventListenerCount--;
@@ -830,7 +915,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Document: ShadyDom.extendAll({__patched: 'Document'},
NodeMixin, FragmentMixin, ElementMixin, UnderActiveElementMixin),
Event: EventMixin
Event: EventMixin,
CustomEvent: ShadyDom.extendAll({__patched: 'CustomEvent'}, EventMixin)
};
ShadyDom.ownerRootForNode = mixinImpl.ownerRootForNode;

View File

@@ -94,7 +94,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
case Node.COMMENT_NODE:
return Mixins.Node;
}
if (obj instanceof Event) {
if (obj instanceof ShadyDom.origEvent) {
return Mixins.Event;
}
},
@@ -139,6 +139,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
function patchEvents() {
Node.prototype.addEventListener = ShadyDom.addEventListener;
Node.prototype.removeEventListener = ShadyDom.removeEventListener;
window.Event = ShadyDom.PatchedEvent;
window.CustomEvent = ShadyDom.PatchedCustomEvent;
}
if (ShadyDom.force || !Element.prototype.createShadowRoot) {

170
test/unit/shady-events.html Normal file
View File

@@ -0,0 +1,170 @@
<!doctype html>
<!--
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<script>
ShadyDom = {force: true};
</script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id="x-event-patched">
<template>
<div>
<div id="candidate" on-foo="fooHandler"></div>
</div>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
properties: {
events: {
type: Object,
value: function() {
return {};
}
}
},
listeners: {
"foo": "hostFooHandler"
},
is: 'x-event-patched',
connected: function() {
this.listen(this.shadowRoot, "rootFooHandler");
},
fooHandler: function(e) {
this.events.div = {
path: e.composedPath(),
target: e.target
};
},
hostFooHandler: function(e) {
this.events.host = {
path: e.composedPath && e.composedPath(),
target: e.target
};
},
rootFooHandler: function(e) {
this.events.root = {
path: e.composedPath && e.composedPath(),
target: e.target
};
}
});
});
</script>
</dom-module>
<dom-module id="x-event-scoped">
<template>
<div id="scoped" on-composed="childHandler" on-scoped="childHandler"></div>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-event-scoped',
properties: {
hostEvents: {
type: Array,
value: function() {
return [];
}
},
childEvents: {
type: Array,
value: function() {
return [];
}
}
},
listeners: {
'composed': 'hostHandler',
'scoped': 'hostHandler'
},
hostHandler: function(e) {
this.hostEvents.push(e.type);
},
childHandler: function(e) {
this.childEvents.push(e.type);
},
fireComposed: function() {
this.fire('composed', null, {node: this.$.scoped});
},
fireScoped: function(){
this.fire('scoped', null, {node: this.$.scoped, composed: false});
}
});
});
</script>
</dom-module>
<test-fixture id="patch">
<template>
<x-event-patched></x-event-patched>
</template>
</test-fixture>
<test-fixture id="globalpatch">
<template>
<div></div>
</template>
</test-fixture>
<test-fixture id="scoping">
<template>
<x-event-scoped></x-event-scoped>
</template>
</test-fixture>
<script>
suite('ShadyDOM event patching', function() {
test('event.composedPath is consistent', function() {
var el = fixture('patch');
var e = new Event('foo', {bubbles: true, composed: true})
el.$.candidate.dispatchEvent(e);
assert.property(el.events, 'div');
assert.equal(el.events.div.target, el.$.candidate);
assert.property(el.events, 'host');
assert.equal(el.events.host.path, el.events.div.path);
assert.equal(el.events.host.target, el);
// assert.property(el.events, 'root');
// assert.equal(el.events.root.path, el.events.div.path);
// assert.equal(el.events.root.target, el.$.candidate);
});
test('event patching works on non Polymer elements', function() {
var el = fixture('globalpatch');
var path;
el.addEventListener('foo', function(e) {
path = e.composedPath();
});
var e = new Event('foo', {composed: true});
el.dispatchEvent(e);
assert.deepEqual([el, el.parentNode, document.body, document.documentElement, document, window], path);
});
test('events handle the `composed` flag correctly', function() {
var el = fixture('scoping');
el.fireScoped();
el.fireComposed();
assert.equal(el.hostEvents.length, 1);
assert.equal(el.hostEvents[0], 'composed');
assert.equal(el.childEvents.length, 2);
assert.equal(el.childEvents[0], 'scoped');
assert.equal(el.childEvents[1], 'composed');
});
});
</script>
</body>
</html>