• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

preactjs / preact / 6065074732

03 Sep 2023 02:25PM UTC coverage: 99.294% (-0.3%) from 99.573%
6065074732

push

github

web-flow
fix: add timer to event handler so we can check whether it was attached during the current propagation (#4126)

* add timer to event handler so we can check whether it was attached during the current propagation

* improve performance by only applying _dispatched on a bubbling event

* remove bubbles

1165 of 1195 branches covered (0.0%)

7 of 7 new or added lines in 1 file covered. (100.0%)

703 of 708 relevant lines covered (99.29%)

823.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.55
/src/diff/props.js
1
import { IS_NON_DIMENSIONAL } from '../constants';
2
import options from '../options';
3

4
/**
5
 * Diff the old and new properties of a VNode and apply changes to the DOM node
6
 * @param {import('../internal').PreactElement} dom The DOM node to apply
7
 * changes to
8
 * @param {object} newProps The new props
9
 * @param {object} oldProps The old props
10
 * @param {boolean} isSvg Whether or not this node is an SVG node
11
 * @param {boolean} hydrate Whether or not we are in hydration mode
12
 */
13
export function diffProps(dom, newProps, oldProps, isSvg, hydrate) {
14
        let i;
15

16
        for (i in oldProps) {
3,344✔
17
                if (i !== 'children' && i !== 'key' && !(i in newProps)) {
1,215✔
18
                        setProperty(dom, i, null, oldProps[i], isSvg);
19
                }
20
        }
21

22
        for (i in newProps) {
3,344✔
23
                if (
24
                        (!hydrate || typeof newProps[i] == 'function') &&
3,654✔
25
                        i !== 'children' &&
26
                        i !== 'key' &&
27
                        i !== 'value' &&
28
                        i !== 'checked' &&
29
                        oldProps[i] !== newProps[i]
30
                ) {
31
                        setProperty(dom, i, newProps[i], oldProps[i], isSvg);
32
                }
33
        }
34
}
35

36
function setStyle(style, key, value) {
37
        if (key[0] === '-') {
46✔
38
                style.setProperty(key, value == null ? '' : value);
12✔
39
        } else if (value == null) {
34✔
40
                style[key] = '';
41
        } else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
73✔
42
                style[key] = value;
43
        } else {
44
                style[key] = value + 'px';
45
        }
46
}
47

48
/**
49
 * Set a property value on a DOM node
50
 * @param {import('../internal').PreactElement} dom The DOM node to modify
51
 * @param {string} name The name of the property to set
52
 * @param {*} value The value to set the property to
53
 * @param {*} oldValue The old value the property had
54
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node or not
55
 */
56
export function setProperty(dom, name, value, oldValue, isSvg) {
57
        let useCapture;
58

59
        o: if (name === 'style') {
608✔
60
                if (typeof value == 'string') {
41✔
61
                        dom.style.cssText = value;
6✔
62
                } else {
63
                        if (typeof oldValue == 'string') {
37✔
64
                                dom.style.cssText = oldValue = '';
65
                        }
66

67
                        if (oldValue) {
35✔
68
                                for (name in oldValue) {
20✔
69
                                        if (!(value && name in value)) {
21✔
70
                                                setStyle(dom.style, name, '');
71
                                        }
72
                                }
73
                        }
74

75
                        if (value) {
35✔
76
                                for (name in value) {
34✔
77
                                        if (!oldValue || value[name] !== oldValue[name]) {
49✔
78
                                                setStyle(dom.style, name, value[name]);
79
                                        }
80
                                }
81
                        }
82
                }
567✔
83
        }
84
        // Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
85
        else if (name[0] === 'o' && name[1] === 'n') {
723✔
86
                useCapture =
155✔
87
                        name !== (name = name.replace(/(PointerCapture)$|Capture$/, '$1'));
88

89
                // Infer correct casing for DOM built-in events:
90
                if (name.toLowerCase() in dom) name = name.toLowerCase().slice(2);
155✔
91
                else name = name.slice(2);
92

93
                if (!dom._listeners) dom._listeners = {};
257✔
94
                dom._listeners[name + useCapture] = value;
95

96
                if (value) {
155✔
97
                        if (!oldValue) {
141✔
98
                                value._attached = Date.now();
99
                                const handler = useCapture ? eventProxyCapture : eventProxy;
111✔
100
                                dom.addEventListener(name, handler, useCapture);
101
                        } else {
102
                                value._attached = oldValue._attached;
103
                        }
104
                } else {
105
                        const handler = useCapture ? eventProxyCapture : eventProxy;
14!
106
                        dom.removeEventListener(name, handler, useCapture);
107
                }
108
        } else if (name !== 'dangerouslySetInnerHTML') {
412✔
109
                if (isSvg) {
400✔
110
                        // Normalize incorrect prop usage for SVG:
111
                        // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed)
112
                        // - className --> class
113
                        name = name.replace(/xlink(H|:h)/, 'h').replace(/sName$/, 's');
73✔
114
                } else if (
327✔
115
                        name !== 'width' &&
3,137✔
116
                        name !== 'height' &&
117
                        name !== 'href' &&
118
                        name !== 'list' &&
119
                        name !== 'form' &&
120
                        // Default value in browsers is `-1` and an empty string is
121
                        // cast to `0` instead
122
                        name !== 'tabIndex' &&
123
                        name !== 'download' &&
124
                        name !== 'rowSpan' &&
125
                        name !== 'colSpan' &&
126
                        name in dom
127
                ) {
128
                        try {
199✔
129
                                dom[name] = value == null ? '' : value;
199✔
130
                                // labelled break is 1b smaller here than a return statement (sorry)
131
                                break o;
195✔
132
                        } catch (e) {}
133
                }
134

135
                // aria- and data- attributes have no boolean representation.
136
                // A `false` value is different from the attribute not being
137
                // present, so we can't remove it. For non-boolean aria
138
                // attributes we could treat false as a removal, but the
139
                // amount of exceptions would cost too many bytes. On top of
140
                // that other frameworks generally stringify `false`.
141

142
                if (typeof value === 'function') {
205✔
143
                        // never serialize functions as attribute values
144
                } else if (value != null && (value !== false || name[4] === '-')) {
600✔
145
                        dom.setAttribute(name, value);
146
                } else {
147
                        dom.removeAttribute(name);
148
                }
149
        }
150
}
151

152
/**
153
 * Proxy an event to hooked event handlers
154
 * @param {Event} e The event object from the browser
155
 * @private
156
 */
157
function eventProxy(e) {
158
        const eventHandler = this._listeners[e.type + false];
60✔
159
        /**
160
         * This trick is inspired by Vue https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/modules/events.ts#L90-L101
161
         * when the dom performs an event it leaves micro-ticks in between bubbling up which means that an event can trigger on a newly
162
         * created DOM-node while the event bubbles up, this can cause quirky behavior as seen in https://github.com/preactjs/preact/issues/3927
163
         */
164
        if (!e._dispatched) {
60!
165
                // When an event has no _dispatched we know this is the first event-target in the chain
166
                // so we set the initial dispatched time.
167
                e._dispatched = Date.now();
60✔
168
                // When the _dispatched is smaller than the time when the targetted event handler was attached
169
                // we know we have bubbled up to an element that was added during patching the dom.
170
        } else if (e._dispatched <= eventHandler._attached) {
×
171
                return;
×
172
        }
173
        return eventHandler(options.event ? options.event(e) : e);
60✔
174
}
175

176
function eventProxyCapture(e) {
177
        return this._listeners[e.type + true](options.event ? options.event(e) : e);
5✔
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc