@openfin/web-utils 0.41.108 → 0.41.111

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;
@@ -9,6 +14,7 @@ declare const mixin: (Base: typeof HTMLElement) => {
9
14
  src: string | null;
10
15
  providerId: string | null;
11
16
  contextGroup: string | null;
17
+ allow: string | null;
12
18
  accessKey: string;
13
19
  readonly accessKeyLabel: string;
14
20
  autocapitalize: string;
@@ -25,7 +31,6 @@ declare const mixin: (Base: typeof HTMLElement) => {
25
31
  readonly offsetWidth: number;
26
32
  outerText: string;
27
33
  spellcheck: boolean;
28
- title: string;
29
34
  translate: boolean;
30
35
  attachInternals(): ElementInternals;
31
36
  click(): void;
@@ -328,6 +333,7 @@ export type OfViewAttributes = {
328
333
  'of-name': string;
329
334
  'of-broker'?: string;
330
335
  'forceFrameName'?: string;
336
+ 'allow'?: string;
331
337
  'src': string;
332
338
  };
333
339
  type OfViewElementConstructor = ReturnType<typeof mixin>;
@@ -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,18 +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;
13
- this.#iframe.style.height = '100%';
14
- this.#iframe.style.width = '100%';
15
- this.#iframe.style.border = 'none';
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;
46
+ if (this.allow) {
47
+ iframe.allow = this.allow;
48
+ }
49
+ iframe.style.height = '100%';
50
+ iframe.style.width = '100%';
51
+ iframe.style.border = 'none';
16
52
  if (this.forceFrameName) {
17
53
  // if forceFrameName is set, the consumer is intentionally breaking auto-connection
18
- this.#iframe.setAttribute('name', this.forceFrameName);
54
+ iframe.setAttribute('name', this.forceFrameName);
19
55
  }
20
56
  else {
21
- this.#iframe.setAttribute('name', encodeOptions({
57
+ iframe.setAttribute('name', encodeOptions({
22
58
  brokerUrl: this.brokerUrl,
23
59
  name: this.name,
24
60
  uuid: this.uuid,
@@ -26,11 +62,23 @@ const mixin = (Base) => class _OfViewElement extends Base {
26
62
  contextGroup: this.contextGroup
27
63
  }, 'of-frame'));
28
64
  }
29
- this.#iframe.setAttribute('id', this.name);
30
- 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;
31
80
  }
32
81
  }
33
- #iframe;
34
82
  get brokerUrl() {
35
83
  return this.getAttribute('of-broker');
36
84
  }
@@ -87,6 +135,14 @@ const mixin = (Base) => class _OfViewElement extends Base {
87
135
  this.setAttribute('of-context-group', val);
88
136
  }
89
137
  }
138
+ get allow() {
139
+ return this.getAttribute('allow');
140
+ }
141
+ set allow(val) {
142
+ if (val) {
143
+ this.setAttribute('allow', val);
144
+ }
145
+ }
90
146
  static get observedAttributes() {
91
147
  return ['name'];
92
148
  }
@@ -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.41.108",
3
+ "version": "0.41.111",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "files": [