@openfin/web-utils 0.42.19 → 0.42.21

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,7 +1,12 @@
1
1
  declare const mixin: (Base: typeof HTMLElement) => {
2
2
  new (): {
3
+ "__#1@#titleObserver": MutationObserver | null;
4
+ "__#1@#initObservers"(): void;
5
+ "__#1@#clearObservers"(): void;
6
+ readonly lastKnownUrl: string | undefined;
3
7
  connectedCallback(): void;
4
- "__#1@#iframe": HTMLIFrameElement;
8
+ readonly iframe: HTMLIFrameElement | null;
9
+ title: string;
5
10
  brokerUrl: string | null;
6
11
  name: string | null;
7
12
  forceFrameName: string | null;
@@ -26,7 +31,6 @@ declare const mixin: (Base: typeof HTMLElement) => {
26
31
  readonly offsetWidth: number;
27
32
  outerText: string;
28
33
  spellcheck: boolean;
29
- title: string;
30
34
  translate: boolean;
31
35
  attachInternals(): ElementInternals;
32
36
  click(): void;
@@ -1,5 +1,31 @@
1
1
  import { encodeOptions } from '../utils/options-encoder';
2
+ import { getTitleObserver, isSameOrigin } from '../utils/utils';
2
3
  const mixin = (Base) => class _OfViewElement extends Base {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.#titleObserver = null;
7
+ }
8
+ #titleObserver;
9
+ #initObservers() {
10
+ // we can only monitor the DOM if the iframe is same-origin
11
+ const iframe = this.iframe;
12
+ if (iframe &&
13
+ iframe.contentDocument?.head &&
14
+ iframe.contentWindow &&
15
+ window.top &&
16
+ isSameOrigin(iframe.contentWindow.window, window.top)) {
17
+ this.#titleObserver = getTitleObserver(iframe.contentDocument.head, (title) => this.dispatchEvent(new CustomEvent('page-title-updated', { detail: { title } })));
18
+ }
19
+ }
20
+ #clearObservers() {
21
+ if (this.#titleObserver) {
22
+ this.#titleObserver.disconnect();
23
+ this.#titleObserver = null;
24
+ }
25
+ }
26
+ get lastKnownUrl() {
27
+ return this.iframe?.contentDocument?.location.href;
28
+ }
3
29
  connectedCallback() {
4
30
  if (!this.name || !this.uuid) {
5
31
  throw new Error('<of-view> Name or uuid attribute missing');
@@ -7,21 +33,28 @@ const mixin = (Base) => class _OfViewElement extends Base {
7
33
  if (!this.src) {
8
34
  throw new Error(`<of-view> missing 'src' attribute.`);
9
35
  }
10
- if (!this.#iframe) {
11
- this.#iframe = document.createElement('iframe');
12
- this.#iframe.src = this.src;
36
+ if (!this.iframe) {
37
+ const iframe = document.createElement('iframe');
38
+ iframe.addEventListener('load', () => {
39
+ // reload observers every navigation
40
+ this.#initObservers();
41
+ });
42
+ iframe.addEventListener('unload', () => {
43
+ this.#clearObservers();
44
+ });
45
+ iframe.src = this.src;
13
46
  if (this.allow) {
14
- this.#iframe.allow = this.allow;
47
+ iframe.allow = this.allow;
15
48
  }
16
- this.#iframe.style.height = '100%';
17
- this.#iframe.style.width = '100%';
18
- this.#iframe.style.border = 'none';
49
+ iframe.style.height = '100%';
50
+ iframe.style.width = '100%';
51
+ iframe.style.border = 'none';
19
52
  if (this.forceFrameName) {
20
53
  // if forceFrameName is set, the consumer is intentionally breaking auto-connection
21
- this.#iframe.setAttribute('name', this.forceFrameName);
54
+ iframe.setAttribute('name', this.forceFrameName);
22
55
  }
23
56
  else {
24
- this.#iframe.setAttribute('name', encodeOptions({
57
+ iframe.setAttribute('name', encodeOptions({
25
58
  brokerUrl: this.brokerUrl,
26
59
  name: this.name,
27
60
  uuid: this.uuid,
@@ -29,11 +62,23 @@ const mixin = (Base) => class _OfViewElement extends Base {
29
62
  contextGroup: this.contextGroup
30
63
  }, 'of-frame'));
31
64
  }
32
- this.#iframe.setAttribute('id', this.name);
33
- this.appendChild(this.#iframe);
65
+ iframe.setAttribute('id', this.name);
66
+ this.appendChild(iframe);
67
+ }
68
+ }
69
+ get iframe() {
70
+ return this.querySelector(`iframe[id="${this.name}"]`);
71
+ }
72
+ get title() {
73
+ return this.getAttribute('title') ?? this.iframe?.title ?? '';
74
+ }
75
+ set title(val) {
76
+ this.setAttribute('title', val);
77
+ // keep iframe title attribute in sync with of-view
78
+ if (this.iframe) {
79
+ this.iframe.title = val;
34
80
  }
35
81
  }
36
- #iframe;
37
82
  get brokerUrl() {
38
83
  return this.getAttribute('of-broker');
39
84
  }
@@ -1 +1,2 @@
1
1
  export * from './options-encoder';
2
+ export * from './utils';
package/out/utils/main.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from './options-encoder';
2
+ export * from './utils';
@@ -0,0 +1,2 @@
1
+ export declare const isSameOrigin: (windowA: Window | WindowProxy, windowB: Window | WindowProxy) => boolean;
2
+ export declare const getTitleObserver: (documentHead: Node, setter: (title: string | null) => void) => MutationObserver;
@@ -0,0 +1,50 @@
1
+ export const isSameOrigin = (windowA, windowB) => {
2
+ try {
3
+ return windowA.origin === windowB.origin;
4
+ }
5
+ catch (error) {
6
+ // CORS errors on access of window proxy origin means the windows are in different origins.
7
+ return false;
8
+ }
9
+ };
10
+ export const getTitleObserver = (documentHead, setter) => {
11
+ const observer = new MutationObserver((mutations) => {
12
+ let shouldEmit = false;
13
+ mutations.forEach((mutation) => {
14
+ // title DOM structure looks like this <title><text_node /><title> and all mutations boil down to:
15
+ // - mutation.target is <text_node>: text_node changed
16
+ // - mutation.target is <title> : text_node either added or removed
17
+ // - mutation.target is <head> : title is either added or removed
18
+ // check easy cases first and early return
19
+ // case 1
20
+ if (mutation.target.parentNode?.nodeName === 'TITLE') {
21
+ shouldEmit = true;
22
+ return;
23
+ }
24
+ // case 2
25
+ if (mutation.target.nodeName === 'TITLE') {
26
+ shouldEmit = true;
27
+ return;
28
+ }
29
+ // case 3 - check removed nodes first
30
+ mutation.removedNodes.forEach((node) => {
31
+ if (node.nodeName === 'TITLE' || node.parentNode?.nodeName === 'TITLE') {
32
+ shouldEmit = true;
33
+ }
34
+ });
35
+ mutation.addedNodes.forEach((node) => {
36
+ if (node.nodeName === 'TITLE' || node.parentNode?.nodeName === 'TITLE') {
37
+ shouldEmit = true;
38
+ }
39
+ });
40
+ });
41
+ // don't use mutation record for values, just grab a new title elem from DOM
42
+ const title = documentHead.querySelector('title')?.textContent ?? '';
43
+ if (shouldEmit) {
44
+ setter(title);
45
+ }
46
+ });
47
+ observer.observe(documentHead, { childList: true, subtree: true, characterData: true });
48
+ return observer;
49
+ };
50
+ // TODO: getFavIconObserver()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/web-utils",
3
- "version": "0.42.19",
3
+ "version": "0.42.21",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "files": [