@openfin/web-utils 0.45.69 → 0.45.70

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.
@@ -1,9 +1,30 @@
1
1
  declare const mixin: (Base: typeof HTMLElement) => {
2
2
  new (): {
3
3
  "__#1@#titleObserver": MutationObserver | null;
4
+ "__#1@#faviconObserver": MutationObserver | null;
5
+ "__#1@#isLoaded": boolean;
6
+ /**
7
+ * The last favicon URL dispatched by this element, regardless of which path
8
+ * discovered it (same-origin DOM or data channel).
9
+ * Set via `#emitFaviconEvent` for element-driven paths, or written directly
10
+ * by `LayoutView.setFavicon` for the data-channel path.
11
+ */
12
+ favicon: string | null;
13
+ /** Centralised favicon dispatch: caches the URL and fires the DOM event. */
14
+ "__#1@#emitFaviconEvent"(favicon: string): void;
4
15
  "__#1@#initObservers"(): void;
5
16
  "__#1@#clearObservers"(): void;
6
17
  readonly lastKnownUrl: string | undefined;
18
+ /**
19
+ * Returns the favicon URL for this view's same-origin iframe, or `null` if the
20
+ * frame is cross-origin, not yet loaded, or has no favicon link element.
21
+ *
22
+ * For same-origin frames the value is read synchronously from the DOM and the
23
+ * favicon MutationObserver is (re-)initialised so future changes are tracked.
24
+ * Cross-origin frames without the OpenFin connect script return `null` — favicon
25
+ * updates for those frames are delivered exclusively via the data channel.
26
+ */
27
+ getOrDiscoverFavicon(): Promise<string | null>;
7
28
  connectedCallback(): void;
8
29
  readonly iframe: HTMLIFrameElement | null;
9
30
  title: string;
@@ -15,6 +36,7 @@ declare const mixin: (Base: typeof HTMLElement) => {
15
36
  providerId: string | null;
16
37
  contextGroup: string | null;
17
38
  allow: string | null;
39
+ showFavicon: boolean;
18
40
  accessKey: string;
19
41
  readonly accessKeyLabel: string;
20
42
  autocapitalize: string;
@@ -1,11 +1,27 @@
1
1
  import { encodeOptions } from '../utils/options-encoder';
2
- import { getTitleObserver, isSameOrigin } from '../utils/utils';
2
+ import { getFaviconObserver, getTitleObserver, isSameOrigin } from '../utils/utils';
3
3
  const mixin = (Base) => class _OfViewElement extends Base {
4
4
  constructor() {
5
5
  super(...arguments);
6
6
  this.#titleObserver = null;
7
+ this.#faviconObserver = null;
8
+ this.#isLoaded = false;
9
+ /**
10
+ * The last favicon URL dispatched by this element, regardless of which path
11
+ * discovered it (same-origin DOM or data channel).
12
+ * Set via `#emitFaviconEvent` for element-driven paths, or written directly
13
+ * by `LayoutView.setFavicon` for the data-channel path.
14
+ */
15
+ this.favicon = null;
7
16
  }
8
17
  #titleObserver;
18
+ #faviconObserver;
19
+ #isLoaded;
20
+ /** Centralised favicon dispatch: caches the URL and fires the DOM event. */
21
+ #emitFaviconEvent(favicon) {
22
+ this.favicon = favicon;
23
+ this.dispatchEvent(new CustomEvent('page-favicon-updated', { detail: { favicon } }));
24
+ }
9
25
  #initObservers() {
10
26
  // we can only monitor the DOM if the iframe is same-origin
11
27
  const iframe = this.iframe;
@@ -17,6 +33,13 @@ const mixin = (Base) => class _OfViewElement extends Base {
17
33
  this.#titleObserver = getTitleObserver(iframe.contentDocument.head, (title) => this.dispatchEvent(new CustomEvent('page-title-updated', { detail: { title } })));
18
34
  // Emit initial document.title
19
35
  this.dispatchEvent(new CustomEvent('page-title-updated', { detail: { title: iframe.contentDocument.title } }));
36
+ if (this.showFavicon) {
37
+ this.#faviconObserver = getFaviconObserver(iframe.contentDocument.head, (favicon) => this.#emitFaviconEvent(favicon));
38
+ const initialFavicon = iframe.contentDocument.querySelector('link[rel~="icon"]')?.href;
39
+ if (initialFavicon) {
40
+ this.#emitFaviconEvent(initialFavicon);
41
+ }
42
+ }
20
43
  }
21
44
  }
22
45
  #clearObservers() {
@@ -24,10 +47,42 @@ const mixin = (Base) => class _OfViewElement extends Base {
24
47
  this.#titleObserver.disconnect();
25
48
  this.#titleObserver = null;
26
49
  }
50
+ if (this.#faviconObserver) {
51
+ this.#faviconObserver.disconnect();
52
+ this.#faviconObserver = null;
53
+ }
27
54
  }
28
55
  get lastKnownUrl() {
29
56
  return this.iframe?.contentDocument?.location.href;
30
57
  }
58
+ /**
59
+ * Returns the favicon URL for this view's same-origin iframe, or `null` if the
60
+ * frame is cross-origin, not yet loaded, or has no favicon link element.
61
+ *
62
+ * For same-origin frames the value is read synchronously from the DOM and the
63
+ * favicon MutationObserver is (re-)initialised so future changes are tracked.
64
+ * Cross-origin frames without the OpenFin connect script return `null` — favicon
65
+ * updates for those frames are delivered exclusively via the data channel.
66
+ */
67
+ getOrDiscoverFavicon() {
68
+ if (!this.#isLoaded)
69
+ return Promise.resolve(null);
70
+ const iframe = this.iframe;
71
+ if (!iframe)
72
+ return Promise.resolve(null);
73
+ const isSame = !!(iframe.contentDocument?.head &&
74
+ iframe.contentWindow &&
75
+ window.top &&
76
+ isSameOrigin(iframe.contentWindow, window.top));
77
+ if (isSame && iframe.contentDocument) {
78
+ // Ensure the favicon observer is running for future DOM changes.
79
+ if (!this.#faviconObserver) {
80
+ this.#faviconObserver = getFaviconObserver(iframe.contentDocument.head, (favicon) => this.#emitFaviconEvent(favicon));
81
+ }
82
+ return Promise.resolve(iframe.contentDocument.querySelector('link[rel~="icon"]')?.href ?? null);
83
+ }
84
+ return Promise.resolve(null);
85
+ }
31
86
  connectedCallback() {
32
87
  if (!this.name || !this.uuid) {
33
88
  throw new Error('<of-view> Name or uuid attribute missing');
@@ -38,6 +93,7 @@ const mixin = (Base) => class _OfViewElement extends Base {
38
93
  if (!this.iframe) {
39
94
  const iframe = document.createElement('iframe');
40
95
  iframe.addEventListener('load', () => {
96
+ this.#isLoaded = true;
41
97
  // reload observers every navigation
42
98
  this.#initObservers();
43
99
  });
@@ -145,6 +201,17 @@ const mixin = (Base) => class _OfViewElement extends Base {
145
201
  this.setAttribute('allow', val);
146
202
  }
147
203
  }
204
+ get showFavicon() {
205
+ return this.hasAttribute('of-show-favicon');
206
+ }
207
+ set showFavicon(val) {
208
+ if (val) {
209
+ this.setAttribute('of-show-favicon', '');
210
+ }
211
+ else {
212
+ this.removeAttribute('of-show-favicon');
213
+ }
214
+ }
148
215
  static get observedAttributes() {
149
216
  return ['name'];
150
217
  }
@@ -1,2 +1,12 @@
1
1
  export declare const isSameOrigin: (windowA: Window | WindowProxy, windowB: Window | WindowProxy) => boolean;
2
2
  export declare const getTitleObserver: (documentHead: Node, setter: (title: string | null) => void) => MutationObserver;
3
+ /**
4
+ * Observe the canonical `link[rel~="icon"]` element in a document and report its href when it
5
+ * changes. The callback is only invoked when the resolved href differs from the last emitted
6
+ * value, preventing spurious re-emissions on unrelated `<head>` mutations.
7
+ *
8
+ * **Cross-origin / Core Web connect:** dynamic favicon detection requires the loaded page to be
9
+ * same-origin with the shell, or to use OpenFin Core Web `connect` to establish the data channel.
10
+ * Arbitrary third-party pages without the OpenFin connect script will not update tab icons dynamically.
11
+ */
12
+ export declare const getFaviconObserver: (documentHead: HTMLHeadElement, callback: (favicon: string) => void) => MutationObserver;
@@ -47,4 +47,32 @@ export const getTitleObserver = (documentHead, setter) => {
47
47
  observer.observe(documentHead, { childList: true, subtree: true, characterData: true });
48
48
  return observer;
49
49
  };
50
- // TODO: getFavIconObserver()
50
+ /**
51
+ * Observe the canonical `link[rel~="icon"]` element in a document and report its href when it
52
+ * changes. The callback is only invoked when the resolved href differs from the last emitted
53
+ * value, preventing spurious re-emissions on unrelated `<head>` mutations.
54
+ *
55
+ * **Cross-origin / Core Web connect:** dynamic favicon detection requires the loaded page to be
56
+ * same-origin with the shell, or to use OpenFin Core Web `connect` to establish the data channel.
57
+ * Arbitrary third-party pages without the OpenFin connect script will not update tab icons dynamically.
58
+ */
59
+ export const getFaviconObserver = (documentHead, callback) => {
60
+ let lastHref;
61
+ const run = () => {
62
+ const href = documentHead.querySelector('link[rel~="icon"]')?.href;
63
+ if (href && href !== lastHref) {
64
+ lastHref = href;
65
+ callback(href);
66
+ }
67
+ };
68
+ const observer = new MutationObserver(() => {
69
+ run();
70
+ });
71
+ observer.observe(documentHead, {
72
+ childList: true,
73
+ subtree: true,
74
+ attributes: true,
75
+ attributeFilter: ['href', 'rel']
76
+ });
77
+ return observer;
78
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/web-utils",
3
- "version": "0.45.69",
3
+ "version": "0.45.70",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "publishConfig": {