How to use commitPassiveHookEffects method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

flushPassiveEffectsImpl.js

Source: flushPassiveEffectsImpl.js Github

copy
1function flushPassiveEffectsImpl() {
2    if (rootWithPendingPassiveEffects === null) {
3        return false;
4    }
5
6    var root = rootWithPendingPassiveEffects;
7    var expirationTime = pendingPassiveEffectsExpirationTime;
8    rootWithPendingPassiveEffects = null;
9    pendingPassiveEffectsExpirationTime = NoWork;
10
11    (function () {
12        if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
13            {
14                throw ReactError(Error("Cannot flush passive effects while already rendering."));
15            }
16        }
17    })();
18
19    var prevExecutionContext = executionContext;
20    executionContext |= CommitContext;
21    var prevInteractions = pushInteractions(root); // Note: This currently assumes there are no passive effects on the root
22    // fiber, because the root is not part of its own effect list. This could
23    // change in the future.
24
25    var effect = root.current.firstEffect;
26
27    while (effect !== null) {
28        {
29            setCurrentFiber(effect);
30            invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
31
32            if (hasCaughtError()) {
33                (function () {
34                    if (!(effect !== null)) {
35                        {
36                            throw ReactError(Error("Should be working on an effect."));
37                        }
38                    }
39                })();
40
41                var error = clearCaughtError();
42                captureCommitPhaseError(effect, error);
43            }
44
45            resetCurrentFiber();
46        }
47
48        var nextNextEffect = effect.nextEffect; // Remove nextEffect pointer to assist GC
49
50        effect.nextEffect = null;
51        effect = nextNextEffect;
52    }
53
54    if (enableSchedulerTracing) {
55        popInteractions(prevInteractions);
56        finishPendingInteractions(root, expirationTime);
57    }
58
59    executionContext = prevExecutionContext;
60    flushSyncCallbackQueue(); // If additional passive effects were scheduled, increment a counter. If this
61    // exceeds the limit, we'll fire a warning.
62
63    nestedPassiveUpdateCount = rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
64    return true;
65}
Full Screen

ReactFiberCommitWork.js

Source: ReactFiberCommitWork.js Github

copy
1import {
2  Placement,
3  Deletion,
4  Update,
5  PlacementAndUpdate
6} from 'shared/ReactSideEffectTags';
7import {
8  HostRoot,
9  HostText,
10  HostComponent,
11  FunctionComponent
12} from 'shared/ReactWorkTags';
13import {
14  insertBefore,
15  appendChild,
16  commitUpdate,
17  removeChild
18} from 'reactDOM/ReactHostConfig';
19import {
20  Passive
21} from 'shared/ReactSideEffectTags';
22import {
23  NoEffect
24} from 'shared/ReactSideEffectTags';
25import {
26  HasEffect as HookHasEffect,
27  Passive as HookPassive
28} from 'shared/ReactHookEffectTags';
29import * as Scheduler from 'scheduler';
30import { NoWork } from './ReactFiberExpirationTime';
31import { 
32  NoPriority, 
33  NormalPriority, 
34  runWithPriority,
35  flushSyncCallbackQueue
36} from 'scheduler';
37import { 
38  getCurrentExecutionContext,
39  setCurrentExecutionContext,
40  CommitContext
41} from './ReactFiberWorkLoop';
42
43// 在React中commitWork中大部分逻辑是杂糅在workloop中的,我将他们抽离到commitWork
44// 为了在workLoop和当前模块间公用全局变量
45export const globalVariables = {
46  rootWithPendingPassiveEffects: null,
47  rootDoesHavePassiveEffects: false,
48  pendingPassiveEffectsExpirationTime: NoWork,
49  pendingPassiveEffectsRenderPriority: NoPriority
50};
51
52function getHostParentFiber(fiber) {
53  let parent = fiber.return;
54  while(parent) {
55    if (isHostParent(parent)) {
56      return parent;
57    }
58    parent = parent.return;
59  }
60}
61
62function isHostParent(parent) {
63  return (
64    parent.tag === HostRoot ||
65    parent.tag === HostComponent
66  ) 
67}
68
69// 目标DOM需要插入在哪个DOM之前(DOMElement.insertBefore)
70function getHostSibling(fiber) {
71  let node = fiber;
72
73  // 嵌套循环的原因是 fiber节点可能没有对应DOM节点,相应的fiber树层级和DOM树也不一定匹配
74  siblings: while(true) {
75    while (!node.sibling) {
76      // 考虑 fiber.return 是 FunctionComponent,fiber.return.sibling 是 HostCompoennt
77      // 则 fiber.stateNode 和 fiber.return.sibling.stateNode在DOM树上是兄弟关系
78      if (!node.return || isHostParent(node.return)) {
79        return null;
80      }
81      node = node.return;
82    }
83    node.sibling.return = node.return;
84    node = node.sibling;
85
86    // 当前节点不是Host节点,目标节点不能直接插在当前节点之前
87    while (node.tag !== HostComponent && node.tag !== HostText) {
88      if (node.effectTag & Placement) {
89        // 如果当前节点也是需要执行插入操作,再进行一次整个流程
90        continue siblings;
91      }
92      // 节点不是Host节点,但是他的子节点如果是Host节点,则目标DOM和子节点DOM是兄弟节点
93      // 目标DOM应该插入在子节点DOM前面
94      // 如果节点没有子节点,则继续寻找兄弟节点
95      if (!node.child) {
96        continue siblings;
97      } else {
98        node.child.return = node;
99        node = node.child;
100      }
101    }
102    // 到这一步时一定是一个Host节点,判断下该节点是否也是需要插入的节点
103    if (!(node.effectTag & Placement)) {
104      return node.stateNode;
105    }
106  }
107}
108
109function commitPlacement(finishedWork) {
110  const parentFiber = getHostParentFiber(finishedWork);
111  const parentStateNode = parentFiber.stateNode;
112
113  let parent;
114  switch (parentFiber.tag) {
115    case HostComponent:
116      parent = parentStateNode;
117      break;
118    case HostRoot:
119      parent = parentStateNode.containerInfo;
120      break;
121  }
122  // 目标DOM节点需要插入在谁之前
123  const before = getHostSibling(finishedWork);
124  insertOrAppendPlacementNode(finishedWork, before, parent);
125}
126
127function commitWork(current, finishedWork) {
128  switch (finishedWork.tag) {
129    case FunctionComponent:
130      // TODO layoutEffect 在 mutation阶段执行destroy
131      // 由于 layoutEffect 的 create是在commitRoot尾部执行,所以任何fiber的任何layoutEffect destroy会先于任何fiber的任何layoutEffect create执行
132      // 这避免了兄弟fiber互相影响
133      // ex:在同一个commit阶段,一个FunctionComponent的destroy不应该复写掉另一个FunctionComponent在create中设置的ref
134      return;
135    case HostComponent:
136      // 处理组件completeWork产生的updateQueue
137      const instance = finishedWork.stateNode;
138      if (instance) {
139        const updatePayload = finishedWork.updateQueue;
140        finishedWork.updatePayload = null;
141        if (updatePayload) {
142          commitUpdate(instance, updatePayload);
143        }
144      }
145      return;
146  }
147}
148
149function commitUnmount(finishedRoot, current) {
150  // TODO 触发 componentWillUnmout 清除ref等操作
151}
152
153function commitNestedUnmounts(finishedRoot, root) {
154  // 整体采用深度优先遍历 树结构
155  let node = root;
156  while (true) {
157    commitUnmount(finishedRoot, node);
158
159    if (node.child) {
160      node.child.return = node;
161      node = node.child;
162      continue;
163    }
164    if (node === root) {
165      return;
166    }
167
168    while (!node.sibling) {
169      if (!node.return || node.return === root) {
170        return;
171      }
172      node = node.return;
173    }
174    node.sibling.return = node.return;
175    node = node.sibling;
176  }
177}
178
179function unmountHostComponents(finishedRoot, current) {
180  // 当前节点可能是FunctionComponent,或者ClassComponent之类
181  // 即当前节点可能没有对应的DOM节点
182  // 所以有可能需要child、child.sibling遍历
183  // 所以这是个循环的过程
184  let node = current;
185  // 当找到要删除节点的父级DOM节点,该变量置为true,这样当遍历到zi节点时不会再执行寻找父级DOM节点的操作
186  let currentParentIsValid = false;
187
188  let currentParent;
189  
190  while (true) {
191    // 这个循环到目的是找到要删除的目标节点的父级DOM节点
192    if (!currentParentIsValid) {
193      let parent = node.return;
194      findParent: while (true) {
195        const parentStateNode = parent.stateNode;
196        switch (parent.tag) {
197          case HostComponent:
198            currentParent = parentStateNode;
199            break findParent;
200          case HostRoot:
201            currentParent = parentStateNode.containerInfo;
202            break findParent;
203        }
204        parent = parent.return;
205      }
206      currentParentIsValid = true;
207    }
208
209    if (node.tag === HostComponent || node.tag === HostText) {
210      // 我们需要遍历下去每个节点,直到叶子节点,从叶子节点触发 componentWillUnmount,再一直往上到当前节点
211      commitNestedUnmounts(finishedRoot, node);
212
213      removeChild(currentParent, node.stateNode);
214
215    } else {
216      // 同commitNestedUnmounts一样的深度优先遍历
217      commitUnmount(finishedRoot, node);
218
219      if (node.child) {
220        node.child.return = node;
221        node = node.child;
222        continue;
223      }
224    }
225    if (node === current) {
226      return;
227    }
228    while (!node.sibling) {
229      if (!node.return || node.return === current) {
230        return;
231      }
232      node = node.return;
233    }
234    node.sibling.return = node.return;
235    node = node.sibling;
236  }
237
238}
239
240function commitDeletion(finishedRoot, current) {
241  unmountHostComponents(finishedRoot, current);
242}
243
244function insertOrAppendPlacementNode(fiber, before, parent) {
245  const {tag} = fiber;
246  if (tag === HostComponent || tag === HostText) {
247    const stateNode = fiber.stateNode;
248    if (before) {
249      insertBefore(parent, stateNode, before);
250    } else {
251      appendChild(parent, stateNode);
252    }
253  } else {
254    // 当前fiber不是host类型,递归其子fiber
255    const child = fiber.child;
256    if (child) {
257      insertOrAppendPlacementNode(child, before, parent);
258      // 对于ClassComponent FunctionComponent 可能返回一个数组,即有多个需要插入的节点
259      // 所以还需要遍历其兄弟节点执行插入
260      const sibling = child.sibling;
261      while (sibling) {
262        insertOrAppendPlacementNode(sibling, before, parent);
263        sibling = sibling.sibling;
264      }
265    }
266  }
267}
268
269export function flushPassiveEffects() {
270  if (globalVariables.pendingPassiveEffectsRenderPriority !== NoPriority) {
271    // passiveEffects 的优先级按 <= NormalPriority
272    const prioriyLevel = globalVariables.pendingPassiveEffectsRenderPriority > NormalPriority ? NormalPriority : globalVariables.pendingPassiveEffectsRenderPriority;
273    globalVariables.pendingPassiveEffectsRenderPriority = NoPriority;
274    return runWithPriority(prioriyLevel, flushPassiveEffectsImpl);
275  }
276}
277
278// 遍历effectList执行 passive effect
279function flushPassiveEffectsImpl() {
280  // 该变量在commitRoot DOM渲染完成后被赋值
281  if (!globalVariables.rootWithPendingPassiveEffects) {
282    return false;
283  }
284  const root = globalVariables.rootWithPendingPassiveEffects;
285  globalVariables.rootWithPendingPassiveEffects = null;
286  const expirationTime = globalVariables.pendingPassiveEffectsExpirationTime;
287  globalVariables.pendingPassiveEffectsExpirationTime = NoWork;
288
289  const prevExecutionContext = getCurrentExecutionContext();
290  setCurrentExecutionContext(CommitContext);
291
292  let effect = root.current.firstEffect;
293  while (effect) {
294    try {
295      commitPassiveHookEffects(effect);
296    } catch(e) {
297      // TODO captureCommitPhaseError
298      console.warn(e);
299    }
300
301    const nextNextEffect = effect.nextEffect;
302    effect.nextEffect = null;
303    effect = nextNextEffect;
304  }
305  setCurrentExecutionContext(prevExecutionContext);
306  flushSyncCallbackQueue();
307  return true;
308}
309
310// commit阶段的第一项工作(before mutation)
311// 调用ClassComponent getSnapshotBeforeUpdate生命周期钩子
312// 异步调用 前一次useEffect的destroy和下一次的mount
313// 由于 commitHookEffectListUnmount 调用后会马上调用 commitHookEffectListMount,
314// 所以前一次同一个useEffect的destroy和下一次的mount是依次同步调用的
315export function commitBeforeMutationEffects(nextEffect) {
316  if (nextEffect) {
317    // TODO getSnapshotBeforeUpdate生命周期钩子
318    const effectTag = nextEffect.effectTag;
319    if ((effectTag & Passive) !== NoEffect) {
320      // 与 componentDidMount 或 componentDidUpdate 不同,useEffect是在DOM更新后异步调用的
321      // 所以不会阻塞页面渲染,见下文
322      // https://zh-hans.reactjs.org/docs/hooks-effect.html#detailed-explanation
323      if (!globalVariables.rootDoesHavePassiveEffects) {
324        // 标记rootDoesHavePassiveEffects为true,在commitRoot中渲染完DOM后会为rootWithPendingPassiveEffects赋值
325        globalVariables.rootDoesHavePassiveEffects = true;
326        Scheduler.scheduleCallback(Scheduler.NormalPriority ,() => {
327          flushPassiveEffects();
328          return null;
329        });
330      }
331    }
332    nextEffect = nextEffect.nextEffect;
333  }
334  return nextEffect;
335}
336
337// 处理DOM增删查改
338export function commitMutationEffects(root, nextEffect) {
339  while (nextEffect) {
340    const effectTag = nextEffect.effectTag;
341    // 处理 Placement / Update / Deletion,排除其他effectTag干扰
342    const primaryEffectTag = effectTag & (Placement | Deletion | Update);
343    let current;
344    switch (primaryEffectTag) {
345      case Placement:
346        commitPlacement(nextEffect);
347        // 去掉已使用的effectTag
348        nextEffect.effectTag &= ~Placement;
349        break;
350      case Update:
351        current = nextEffect.alternate;
352        commitWork(current, nextEffect);
353        break;
354      case Deletion:
355        commitDeletion(root, nextEffect);
356        break;
357      case PlacementAndUpdate:
358        // Placement
359        commitPlacement(nextEffect);
360        nextEffect.effectTag &= ~Placement;
361        // Update
362        current = nextEffect.alternate;
363        commitWork(current, nextEffect);
364        break;
365    }
366    nextEffect = nextEffect.nextEffect;
367  }
368  return null;
369}
370
371function commitHookEffectListUnmount(tag, finishedWork) {
372  const updateQueue = finishedWork.updateQueue;
373  let lastEffect = updateQueue ? updateQueue.lastEffect : null;
374  if (lastEffect) {
375    const firstEffect = lastEffect.next;
376    let effect = firstEffect;
377    do {
378      if ((effect.tag & tag) === tag) {
379        // unmount
380        const destroy = effect.destroy;
381        effect.destroy = undefined;
382        if (destroy) {
383          destroy();
384        }
385      }
386      effect = effect.next;
387    } while (effect !== firstEffect)
388  }
389}
390
391function commitHookEffectListMount(tag, finishedWork) {
392  const updateQueue = finishedWork.updateQueue;
393  let lastEffect = updateQueue ? updateQueue.lastEffect : null;
394  if (lastEffect) {
395    const firstEffect = lastEffect.next;
396    let effect = firstEffect;
397    do {
398      if ((effect.tag & tag) === tag) {
399        // mount
400        const create = effect.create;
401        effect.destroy = create();
402      }
403      effect = effect.next;
404    } while (effect !== firstEffect)
405  }
406}
407
408function commitPassiveHookEffects(finishedWork) {
409  if ((finishedWork.effectTag & Passive) !== NoEffect) {
410    switch (finishedWork.tag) {
411      case FunctionComponent:
412        // 遍历updateQueue执行 useEffect unmount函数
413        commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork);
414        commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
415        break;
416      default:
417        break;
418    }
419  }
420}
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Playwright Internal on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)