@netless/window-manager 1.0.13-test.2 → 1.0.13-test.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -199,6 +199,25 @@ export type MountParams = {
199
199
  useBoxesStatus?: boolean;
200
200
  };
201
201
 
202
+ type DiagnosticColorChannels = {
203
+ raw: string;
204
+ r?: number;
205
+ g?: number;
206
+ b?: number;
207
+ a?: number;
208
+ };
209
+
210
+ type DiagnosticRect = {
211
+ x: number;
212
+ y: number;
213
+ width: number;
214
+ height: number;
215
+ top: number;
216
+ right: number;
217
+ bottom: number;
218
+ left: number;
219
+ };
220
+
202
221
  export const reconnectRefresher = new ReconnectRefresher({ emitter: internalEmitter });
203
222
  export class WindowManager
204
223
  extends InvisiblePlugin<WindowMangerAttributes, any>
@@ -253,6 +272,7 @@ export class WindowManager
253
272
  super(context);
254
273
  WindowManager.displayer = context.displayer;
255
274
  (window as any).NETLESS_DEPS = __APP_DEPENDENCIES__;
275
+ this.emitter.on('mainViewScenePathChange', this.onMainViewScenePathChangeHandler)
256
276
  }
257
277
 
258
278
  public static onCreate(manager: WindowManager) {
@@ -386,10 +406,438 @@ export class WindowManager
386
406
  console.warn("[WindowManager]: indexedDB open failed");
387
407
  console.log(error);
388
408
  }
389
-
409
+ manager.emitter.on('mainViewScenePathChange', manager.onMainViewScenePathChangeHandler)
390
410
  return manager;
391
411
  }
392
412
 
413
+ public onMainViewScenePathChangeHandler = (scenePath: string) => {
414
+ const mainViewElement = this.mainView.divElement;
415
+ this.logMainViewVisibilityDiagnostics("onMainViewScenePathChange", scenePath, mainViewElement);
416
+ if (mainViewElement) {
417
+ const backgroundImage = mainViewElement.querySelector('.background img');
418
+ if (backgroundImage) {
419
+ const backgroundImageRect = backgroundImage?.getBoundingClientRect();
420
+ const backgroundImageCSS = window.getComputedStyle(backgroundImage);
421
+ const backgroundImageVisible = backgroundImageRect?.width > 0 && backgroundImageRect?.height > 0 && backgroundImageCSS.display !== 'none';
422
+ const camera = this.mainView.camera;
423
+ console.log("[window-manager] backgroundImageVisible:" + backgroundImageVisible + " camera:" + JSON.stringify(camera));
424
+ return;
425
+ }
426
+ console.log("[window-manager] onMainViewScenePathChange scenePath:" + scenePath + ' backgroundImageVisible is not found');
427
+ return;
428
+ }
429
+ console.log("[window-manager] onMainViewScenePathChange scenePath:" + scenePath + ' mainViewElement is not found');
430
+ }
431
+
432
+ public logMainViewVisibilityDiagnostics(
433
+ tag: string,
434
+ scenePath?: string,
435
+ mainViewElement?: HTMLDivElement | null
436
+ ): void {
437
+ const element = mainViewElement ?? this.mainView?.divElement;
438
+ const label = scenePath ? `${tag}:${scenePath}` : tag;
439
+ const payload = this.collectMainViewVisibilityDiagnostics(element, scenePath);
440
+ this.emitMainViewVisibilityDiagnostic(label, payload.summary);
441
+ if (payload.details) {
442
+ this.emitMainViewVisibilityDiagnostic(`${label}:details`, payload.details);
443
+ }
444
+ }
445
+
446
+ private emitMainViewVisibilityDiagnostic(tag: string, payload: unknown): void {
447
+ const content = `[window-manager][visibility][${tag}] ${JSON.stringify(payload)}`;
448
+ // console.log(content);
449
+ this._roomLogger?.info(content);
450
+ }
451
+
452
+ private collectMainViewVisibilityDiagnostics(
453
+ mainViewElement: HTMLDivElement | null | undefined,
454
+ scenePath?: string
455
+ ): {
456
+ summary: Record<string, any>;
457
+ details: Record<string, any> | null;
458
+ } {
459
+ const element = mainViewElement ?? null;
460
+ const backgroundImage = element?.querySelector(".background img") as HTMLImageElement | null;
461
+ const elementDiagnostic = element ? this.collectElementDiagnostic(element) : null;
462
+ const chainDiagnostics = element ? this.collectElementChainDiagnostics(element) : [];
463
+ const elementRect = element?.getBoundingClientRect?.() ?? null;
464
+ const centerPoint = elementRect
465
+ ? {
466
+ x: elementRect.left + elementRect.width / 2,
467
+ y: elementRect.top + elementRect.height / 2,
468
+ }
469
+ : undefined;
470
+ const topElement =
471
+ centerPoint &&
472
+ Number.isFinite(centerPoint.x) &&
473
+ Number.isFinite(centerPoint.y)
474
+ ? document.elementFromPoint(centerPoint.x, centerPoint.y)
475
+ : null;
476
+ const overlayStack =
477
+ centerPoint &&
478
+ Number.isFinite(centerPoint.x) &&
479
+ Number.isFinite(centerPoint.y) &&
480
+ document.elementsFromPoint
481
+ ? document
482
+ .elementsFromPoint(centerPoint.x, centerPoint.y)
483
+ .slice(0, 10)
484
+ .map(item => this.describeElement(item))
485
+ .filter((item): item is string => item !== null)
486
+ : [];
487
+ const topElementDiagnostic = topElement ? this.collectElementDiagnostic(topElement) : null;
488
+ const backgroundImageDiagnostic = backgroundImage
489
+ ? this.collectImageDiagnostic(backgroundImage)
490
+ : null;
491
+ const blockers: string[] = [];
492
+ const warnings: string[] = [];
493
+ const suspiciousAncestors: Array<Record<string, any>> = [];
494
+ const mainViewBlockers: string[] = [];
495
+ const mainViewWarnings: string[] = [];
496
+
497
+ if (!element) {
498
+ blockers.push("mainViewElement.missing");
499
+ }
500
+
501
+ if (document.hidden || document.visibilityState === "hidden") {
502
+ blockers.push("document.hidden");
503
+ }
504
+
505
+ if (elementDiagnostic) {
506
+ this.appendRenderImpactIssues(
507
+ "mainViewElement",
508
+ elementDiagnostic,
509
+ mainViewBlockers,
510
+ mainViewWarnings
511
+ );
512
+ blockers.push(...mainViewBlockers);
513
+ warnings.push(...mainViewWarnings);
514
+ }
515
+
516
+ chainDiagnostics.slice(1).forEach((diagnostic, index) => {
517
+ const ancestorBlockers: string[] = [];
518
+ const ancestorWarnings: string[] = [];
519
+ this.appendRenderImpactIssues(
520
+ `ancestor[${index + 1}]`,
521
+ diagnostic,
522
+ ancestorBlockers,
523
+ ancestorWarnings
524
+ );
525
+ if (ancestorBlockers.length > 0 || ancestorWarnings.length > 0) {
526
+ blockers.push(...ancestorBlockers);
527
+ warnings.push(...ancestorWarnings);
528
+ suspiciousAncestors.push(this.pickRenderRelevantFields(diagnostic));
529
+ }
530
+ });
531
+
532
+ let backgroundImageStatus: Record<string, any> | null = null;
533
+ const backgroundImageBlockers: string[] = [];
534
+ const backgroundImageWarnings: string[] = [];
535
+ if (backgroundImageDiagnostic) {
536
+ backgroundImageStatus = this.pickBackgroundImageStatus(backgroundImageDiagnostic);
537
+ this.appendRenderImpactIssues(
538
+ "backgroundImage",
539
+ backgroundImageDiagnostic,
540
+ backgroundImageBlockers,
541
+ backgroundImageWarnings
542
+ );
543
+ blockers.push(...backgroundImageBlockers);
544
+ warnings.push(...backgroundImageWarnings);
545
+ if (backgroundImageDiagnostic.complete === false) {
546
+ warnings.push("backgroundImage.loading");
547
+ } else if (backgroundImageDiagnostic.naturalWidth === 0) {
548
+ warnings.push("backgroundImage.empty");
549
+ }
550
+ }
551
+
552
+ let topElementSummary: Record<string, any> | null = null;
553
+ if (topElementDiagnostic) {
554
+ const coveredByOutsideElement = Boolean(
555
+ element && topElement && topElement !== element && !element.contains(topElement)
556
+ );
557
+ topElementSummary = {
558
+ node: topElementDiagnostic.node,
559
+ coveredByOutsideElement,
560
+ };
561
+ if (coveredByOutsideElement) {
562
+ warnings.push(`center.coveredBy:${topElementDiagnostic.node}`);
563
+ }
564
+ }
565
+
566
+ const summary: Record<string, any> = {
567
+ scenePath: scenePath || null,
568
+ timestamp: new Date().toISOString(),
569
+ status:
570
+ blockers.length > 0
571
+ ? "blocked"
572
+ : warnings.length > 0
573
+ ? "uncertain"
574
+ : "likely-renderable",
575
+ canRender: blockers.length === 0,
576
+ blockers,
577
+ warnings,
578
+ };
579
+ if (
580
+ backgroundImageStatus &&
581
+ backgroundImageDiagnostic &&
582
+ (backgroundImageDiagnostic.complete === false ||
583
+ backgroundImageDiagnostic.naturalWidth === 0)
584
+ ) {
585
+ summary.backgroundImage = backgroundImageStatus;
586
+ }
587
+ if (topElementSummary?.coveredByOutsideElement) {
588
+ summary.coveringElement = topElementSummary.node;
589
+ }
590
+
591
+ const shouldEmitDetails =
592
+ blockers.length > 0 ||
593
+ warnings.some(
594
+ warning =>
595
+ warning !== "backgroundImage.loading" &&
596
+ warning !== "backgroundImage.empty"
597
+ );
598
+ const details: Record<string, any> = {};
599
+ if ((mainViewBlockers.length > 0 || mainViewWarnings.length > 0) && elementDiagnostic) {
600
+ details.mainViewElement = this.pickRenderRelevantFields(elementDiagnostic);
601
+ }
602
+ if (
603
+ suspiciousAncestors.length > 0
604
+ ) {
605
+ details.suspiciousAncestors = suspiciousAncestors;
606
+ }
607
+ if (
608
+ backgroundImageDiagnostic &&
609
+ (backgroundImageBlockers.length > 0 ||
610
+ backgroundImageWarnings.length > 0 ||
611
+ backgroundImageDiagnostic.complete === false ||
612
+ backgroundImageDiagnostic.naturalWidth === 0)
613
+ ) {
614
+ details.backgroundImage = {
615
+ ...this.pickRenderRelevantFields(backgroundImageDiagnostic),
616
+ loadState: backgroundImageStatus,
617
+ };
618
+ }
619
+ if (topElementSummary?.coveredByOutsideElement && topElementDiagnostic) {
620
+ details.topElementAtCenter = {
621
+ ...this.pickRenderRelevantFields(topElementDiagnostic),
622
+ overlayStack,
623
+ };
624
+ }
625
+
626
+ return {
627
+ summary,
628
+ details: shouldEmitDetails && Object.keys(details).length > 0 ? details : null,
629
+ };
630
+ }
631
+
632
+ private collectElementChainDiagnostics(element: Element): Array<Record<string, any>> {
633
+ const chain: Array<Record<string, any>> = [];
634
+ let current: Element | null = element;
635
+ while (current) {
636
+ const diagnostic = this.collectElementDiagnostic(current);
637
+ if (diagnostic) {
638
+ chain.push(diagnostic);
639
+ }
640
+ current = current.parentElement;
641
+ }
642
+ return chain;
643
+ }
644
+
645
+ private collectImageDiagnostic(image: HTMLImageElement): Record<string, any> {
646
+ const diagnostic = this.collectElementDiagnostic(image);
647
+ return {
648
+ ...diagnostic,
649
+ currentSrc: image.currentSrc,
650
+ src: image.getAttribute("src"),
651
+ complete: image.complete,
652
+ naturalWidth: image.naturalWidth,
653
+ naturalHeight: image.naturalHeight,
654
+ };
655
+ }
656
+
657
+ private appendRenderImpactIssues(
658
+ label: string,
659
+ diagnostic: Record<string, any>,
660
+ blockers: string[],
661
+ warnings: string[]
662
+ ): void {
663
+ const opacity = Number.parseFloat(diagnostic.opacity || "1");
664
+ if (diagnostic.hiddenAttribute) {
665
+ blockers.push(`${label}.hiddenAttribute`);
666
+ }
667
+ if (diagnostic.display === "none") {
668
+ blockers.push(`${label}.display:none`);
669
+ }
670
+ if (diagnostic.visibility === "hidden" || diagnostic.visibility === "collapse") {
671
+ blockers.push(`${label}.visibility:${diagnostic.visibility}`);
672
+ }
673
+ if (Number.isFinite(opacity) && opacity <= 0.01) {
674
+ blockers.push(`${label}.opacity:${diagnostic.opacity}`);
675
+ }
676
+ if (diagnostic.contentVisibility === "hidden") {
677
+ blockers.push(`${label}.contentVisibility:hidden`);
678
+ }
679
+ if (diagnostic.transform !== "none") {
680
+ warnings.push(`${label}.transform`);
681
+ }
682
+ if (diagnostic.filter !== "none") {
683
+ warnings.push(`${label}.filter`);
684
+ }
685
+ if (diagnostic.backdropFilter !== "none") {
686
+ warnings.push(`${label}.backdropFilter`);
687
+ }
688
+ if (diagnostic.clipPath !== "none") {
689
+ warnings.push(`${label}.clipPath`);
690
+ }
691
+ if (diagnostic.maskImage !== "none") {
692
+ warnings.push(`${label}.maskImage`);
693
+ }
694
+ if (diagnostic.mixBlendMode !== "normal") {
695
+ warnings.push(`${label}.mixBlendMode:${diagnostic.mixBlendMode}`);
696
+ }
697
+ }
698
+
699
+ private pickRenderRelevantFields(diagnostic: Record<string, any>): Record<string, any> {
700
+ const result: Record<string, any> = {
701
+ node: diagnostic.node,
702
+ display: diagnostic.display,
703
+ visibility: diagnostic.visibility,
704
+ opacity: diagnostic.opacity,
705
+ hiddenAttribute: diagnostic.hiddenAttribute,
706
+ contentVisibility: diagnostic.contentVisibility,
707
+ backgroundColor: diagnostic.backgroundColor,
708
+ backgroundAlpha: diagnostic.backgroundColorChannels?.a ?? null,
709
+ color: diagnostic.color,
710
+ colorAlpha: diagnostic.colorChannels?.a ?? null,
711
+ textFillColor: diagnostic.textFillColor,
712
+ textFillAlpha: diagnostic.textFillColorChannels?.a ?? null,
713
+ };
714
+ if (diagnostic.transform !== "none") {
715
+ result.transform = diagnostic.transform;
716
+ }
717
+ if (diagnostic.filter !== "none") {
718
+ result.filter = diagnostic.filter;
719
+ }
720
+ if (diagnostic.backdropFilter !== "none") {
721
+ result.backdropFilter = diagnostic.backdropFilter;
722
+ }
723
+ if (diagnostic.mixBlendMode !== "normal") {
724
+ result.mixBlendMode = diagnostic.mixBlendMode;
725
+ }
726
+ if (diagnostic.clipPath !== "none") {
727
+ result.clipPath = diagnostic.clipPath;
728
+ }
729
+ if (diagnostic.maskImage !== "none") {
730
+ result.maskImage = diagnostic.maskImage;
731
+ }
732
+ if (diagnostic.overflow !== "visible") {
733
+ result.overflow = diagnostic.overflow;
734
+ }
735
+ if (diagnostic.zIndex !== "auto") {
736
+ result.zIndex = diagnostic.zIndex;
737
+ }
738
+ return result;
739
+ }
740
+
741
+ private pickBackgroundImageStatus(diagnostic: Record<string, any>): Record<string, any> {
742
+ return {
743
+ found: true,
744
+ complete: diagnostic.complete,
745
+ currentSrc: diagnostic.currentSrc || diagnostic.src || "",
746
+ naturalWidth: diagnostic.naturalWidth,
747
+ naturalHeight: diagnostic.naturalHeight,
748
+ };
749
+ }
750
+
751
+ private collectElementDiagnostic(element: Element | null): Record<string, any> | null {
752
+ if (!element) {
753
+ return null;
754
+ }
755
+ const style = window.getComputedStyle(element);
756
+ const htmlElement = element as HTMLElement;
757
+ return {
758
+ node: this.describeElement(element),
759
+ isConnected: element.isConnected,
760
+ hiddenAttribute: htmlElement.hidden,
761
+ ariaHidden: htmlElement.getAttribute("aria-hidden"),
762
+ opacity: style.opacity,
763
+ alpha: style.opacity,
764
+ display: style.display,
765
+ visibility: style.visibility,
766
+ backgroundColor: style.backgroundColor,
767
+ backgroundColorChannels: this.parseColorChannels(style.backgroundColor),
768
+ color: style.color,
769
+ colorChannels: this.parseColorChannels(style.color),
770
+ textFillColor: style.getPropertyValue("-webkit-text-fill-color"),
771
+ textFillColorChannels: this.parseColorChannels(
772
+ style.getPropertyValue("-webkit-text-fill-color")
773
+ ),
774
+ filter: style.filter,
775
+ backdropFilter: style.getPropertyValue("backdrop-filter"),
776
+ mixBlendMode: style.mixBlendMode,
777
+ transform: style.transform,
778
+ contentVisibility: style.getPropertyValue("content-visibility"),
779
+ clipPath: style.clipPath,
780
+ maskImage:
781
+ style.getPropertyValue("mask-image") ||
782
+ style.getPropertyValue("-webkit-mask-image"),
783
+ overflow: style.overflow,
784
+ zIndex: style.zIndex,
785
+ };
786
+ }
787
+
788
+ private describeElement(element: Element | null): string | null {
789
+ if (!element) {
790
+ return null;
791
+ }
792
+ const tagName = element.tagName.toLowerCase();
793
+ const id = element.id ? `#${element.id}` : "";
794
+ const className =
795
+ typeof (element as HTMLElement).className === "string" &&
796
+ (element as HTMLElement).className.trim()
797
+ ? `.${(element as HTMLElement).className.trim().replace(/\s+/g, ".")}`
798
+ : "";
799
+ return `${tagName}${id}${className}`;
800
+ }
801
+
802
+ private serializeRect(rect?: DOMRect | null): DiagnosticRect | null {
803
+ if (!rect) {
804
+ return null;
805
+ }
806
+ return {
807
+ x: rect.x,
808
+ y: rect.y,
809
+ width: rect.width,
810
+ height: rect.height,
811
+ top: rect.top,
812
+ right: rect.right,
813
+ bottom: rect.bottom,
814
+ left: rect.left,
815
+ };
816
+ }
817
+
818
+ private parseColorChannels(value?: string | null): DiagnosticColorChannels {
819
+ const raw = value?.trim() || "";
820
+ if (!raw) {
821
+ return { raw };
822
+ }
823
+ if (raw === "transparent") {
824
+ return { raw, r: 0, g: 0, b: 0, a: 0 };
825
+ }
826
+ const matches = raw.match(/^rgba?\((.+)\)$/i);
827
+ if (!matches) {
828
+ return { raw };
829
+ }
830
+ const parts = matches[1].split(",").map(part => part.trim());
831
+ const [r, g, b, a] = parts.map(part => Number(part));
832
+ return {
833
+ raw,
834
+ r: Number.isFinite(r) ? r : undefined,
835
+ g: Number.isFinite(g) ? g : undefined,
836
+ b: Number.isFinite(b) ? b : undefined,
837
+ a: Number.isFinite(a) ? a : raw.startsWith("rgb(") ? 1 : undefined,
838
+ };
839
+ }
840
+
393
841
  private static initManager(room: Room): Promise<WindowManager | undefined> {
394
842
  return createInvisiblePlugin(room);
395
843
  }
@@ -462,6 +910,7 @@ export class WindowManager
462
910
  this.appManager.setBoxManager(boxManager);
463
911
  }
464
912
  this.bindMainView(mainViewElement, params.disableCameraTransform);
913
+ this.logMainViewVisibilityDiagnostics("bindContainer.afterBindMainView");
465
914
  if (WindowManager.wrapper) {
466
915
  this.cursorManager?.setupWrapper(WindowManager.wrapper);
467
916
  }
@@ -1011,7 +1460,6 @@ export class WindowManager
1011
1460
  const mainViewCamera = { ...this.mainView.camera };
1012
1461
  if (isEqual({ ...mainViewCamera, ...pureCamera }, mainViewCamera)) return;
1013
1462
  this.mainView.moveCamera(camera);
1014
- // this.appManager?.dispatchInternalEvent(Events.MoveCamera, camera);
1015
1463
  setTimeout(() => {
1016
1464
  this.appManager?.mainViewProxy.setCameraAndSize();
1017
1465
  }, 500);
@@ -1024,7 +1472,6 @@ export class WindowManager
1024
1472
  }>
1025
1473
  ): void {
1026
1474
  this.mainView.moveCameraToContain(rectangle);
1027
- // this.appManager?.dispatchInternalEvent(Events.MoveCameraToContain, rectangle);
1028
1475
  setTimeout(() => {
1029
1476
  this.appManager?.mainViewProxy.setCameraAndSize();
1030
1477
  }, 500);
@@ -1059,6 +1506,7 @@ export class WindowManager
1059
1506
  WindowManager.playground.parentNode?.removeChild(WindowManager.playground);
1060
1507
  }
1061
1508
  WindowManager.params = undefined;
1509
+ this.emitter.off('mainViewScenePathChange', this.onMainViewScenePathChangeHandler);
1062
1510
  this._iframeBridge?.destroy();
1063
1511
  this._iframeBridge = undefined;
1064
1512
  log("Destroyed");