passbolt-styleguide 5.5.0 → 5.5.1

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.
Files changed (27) hide show
  1. package/build/css/help.min.css +2 -2
  2. package/build/css/public.min.css +2 -2
  3. package/build/css/themes/default/api_authentication.min.css +2 -2
  4. package/build/css/themes/default/api_cloud.min.css +2 -2
  5. package/build/css/themes/default/api_main.min.css +2 -2
  6. package/build/css/themes/default/api_reports.min.css +2 -2
  7. package/build/css/themes/default/api_webinstaller.min.css +2 -2
  8. package/build/css/themes/default/ext_app.min.css +2 -2
  9. package/build/css/themes/default/ext_authentication.min.css +2 -2
  10. package/build/css/themes/default/ext_external.min.css +2 -2
  11. package/build/css/themes/default/ext_in_form_cta.min.css +2 -2
  12. package/build/css/themes/default/ext_in_form_menu.min.css +2 -2
  13. package/build/css/themes/default/ext_quickaccess.min.css +2 -2
  14. package/build/css/themes/midgar/api_authentication.min.css +2 -2
  15. package/build/css/themes/midgar/api_main.min.css +2 -2
  16. package/build/css/themes/midgar/api_reports.min.css +2 -2
  17. package/build/css/themes/midgar/ext_app.min.css +2 -2
  18. package/build/css/themes/midgar/ext_authentication.min.css +2 -2
  19. package/build/css/themes/midgar/ext_in_form_cta.min.css +2 -2
  20. package/build/css/themes/midgar/ext_in_form_menu.min.css +2 -2
  21. package/build/css/themes/midgar/ext_quickaccess.min.css +2 -2
  22. package/package.json +1 -1
  23. package/src/react-web-integration/lib/InForm/InFormCallToActionField.js +9 -7
  24. package/src/react-web-integration/lib/InForm/InFormManager.js +111 -4
  25. package/src/react-web-integration/lib/InForm/InformManager.test.js +81 -9
  26. package/src/react-web-integration/lib/InForm/InformManager.test.page.js +1 -8
  27. package/src/react-web-integration/lib/InForm/InformMenuField.js +8 -5
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
@@ -1,7 +1,7 @@
1
1
  /**!
2
2
  * @name passbolt-styleguide
3
- * @version v5.5.0
4
- * @date 2025-09-08
3
+ * @version v5.5.1
4
+ * @date 2025-09-09
5
5
  * @copyright Copyright 2023 Passbolt SA
6
6
  * @source https://github.com/passbolt/passbolt_styleguide
7
7
  * @license AGPL-3.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passbolt-styleguide",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "license": "AGPL-3.0",
5
5
  "copyright": "Copyright 2023 Passbolt SA",
6
6
  "description": "Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.",
@@ -59,8 +59,9 @@ class InFormCallToActionField {
59
59
  * Default constructor
60
60
  * @param field The DOM element
61
61
  * @param fieldType The type of field
62
+ * @param shadowRoot The shadow root
62
63
  */
63
- constructor(field, fieldType) {
64
+ constructor(field, fieldType, shadowRoot) {
64
65
  /** The field to which the in-form is attached */
65
66
  this.field = field;
66
67
  /** Type of the field ("username" or "password") */
@@ -76,6 +77,8 @@ class InFormCallToActionField {
76
77
  /** In-form call-to-action click listener callback */
77
78
  this.callToActionClickCallback = null;
78
79
 
80
+ this.shadowRoot = shadowRoot;
81
+
79
82
  this.bindCallbacks();
80
83
  this.handleInsertionEvent();
81
84
  this.handleRemoveEvent();
@@ -118,7 +121,7 @@ class InFormCallToActionField {
118
121
  * Insert an in-form call-to-action iframe
119
122
  */
120
123
  async insertInformCallToActionIframe() {
121
- const iframes = document.querySelectorAll('iframe');
124
+ const iframes = this.shadowRoot.querySelectorAll('iframe');
122
125
  // Use of Array prototype some method cause NodeList is not an array !
123
126
  const iframeId = this.iframeId;
124
127
  const isIframeAlreadyInserted = Array.prototype.some.call(iframes, iframe => iframe.id === iframeId);
@@ -137,7 +140,7 @@ class InFormCallToActionField {
137
140
  const {top, left} = this.calculateFieldPosition();
138
141
  const portId = await port.request("passbolt.port.generate-id", "InFormCallToAction");
139
142
  const iframe = document.createElement('iframe');
140
- document.body.appendChild(iframe);
143
+ this.shadowRoot.appendChild(iframe);
141
144
  const browserExtensionUrl = browser.runtime.getURL("/");
142
145
  iframe.id = this.iframeId;
143
146
  iframe.style.position = "fixed";
@@ -147,7 +150,6 @@ class InFormCallToActionField {
147
150
  iframe.style.border = "none";
148
151
  iframe.style.width = '18px';
149
152
  iframe.style.height = '18px';
150
- iframe.style.zIndex = "123456"; // For you Yahoo with love
151
153
  iframe.style.colorScheme = "auto"; // To have the transparency on dark theme
152
154
  iframe.contentWindow.location = `${browserExtensionUrl}webAccessibleResources/passbolt-iframe-in-form-call-to-action.html?passbolt=${portId}`;
153
155
  return iframe;
@@ -205,7 +207,7 @@ class InFormCallToActionField {
205
207
  */
206
208
  this.callToActionClickWatcher = setInterval(() => {
207
209
  // Check if a click has been applied on some iframe
208
- const elem = document.activeElement;
210
+ const elem = this.shadowRoot.activeElement;
209
211
  if (elem && elem.tagName === 'IFRAME' && elem.id === this.iframeId) {
210
212
  this.field.focus();
211
213
  this.callToActionClickCallback();
@@ -246,7 +248,7 @@ class InFormCallToActionField {
246
248
  * @param event The mouse-out event
247
249
  */
248
250
  removeInFormCallToActionWhenMouseOut(event) {
249
- const isNotCallToActionIframe = event.relatedTarget && event.relatedTarget.id !== this.iframeId;
251
+ const isNotCallToActionIframe = event.relatedTarget !== this.shadowRoot.host;
250
252
  const isActiveElementAnAuthenticationField = document.activeElement === this.field;
251
253
  if (isNotCallToActionIframe && !isActiveElementAnAuthenticationField) {
252
254
  this.removeCallToActionIframe();
@@ -257,7 +259,7 @@ class InFormCallToActionField {
257
259
  * Remove the call-to-action (iframe)
258
260
  */
259
261
  removeCallToActionIframe() {
260
- const iframes = document.querySelectorAll('iframe');
262
+ const iframes = this.shadowRoot.querySelectorAll('iframe');
261
263
  iframes.forEach(iframe => {
262
264
  const identifierToMatch = this.iframeId;
263
265
  if (iframe.id === identifierToMatch) {
@@ -35,18 +35,65 @@ class InFormManager {
35
35
  this.menuField = null;
36
36
  /** In-form form fields in the target page*/
37
37
  this.credentialsFormFields = [];
38
- /** Mutation observer to detect any change on the DOM */
38
+ /** Mutation observers to detect any change on the DOM */
39
39
  this.mutationObserver = null;
40
40
 
41
+ /** The shadow root with the host **/
42
+ this.host = null;
43
+ this.shadowRoot = null;
44
+
45
+ this.hostMutationObserver = null;
46
+ this.htmlMutationObserver = null;
47
+ this.bodyMutationObserver = null;
48
+
41
49
  this.bindCallbacks();
42
50
  }
43
51
 
52
+ /**
53
+ * Create the shadow host and shadow root and insert in the body
54
+ */
55
+ createAndInsertShadowRootWithHost() {
56
+ this.host = document.createElement('div');
57
+ /*
58
+ * Remove all style the component could have inherited from its environment.
59
+ * Enforce the following style:
60
+ * - position fixed to have the positioning relative to the viewport
61
+ * - display block to ensure the component is always displayed
62
+ * - z-index fixed to the maximum allowed value to ensure the component is always displayed above all the page's components.
63
+ */
64
+ this.host.setAttribute('style', 'all: initial; position: fixed !important; display: block !important; z-index: 2147483647 !important');
65
+ // Block any setter and getter property style, however it can be bypassed with setAttribute.
66
+ Object.defineProperty(this.host, 'style', {
67
+ set: () => {},
68
+ get: () => null,
69
+ });
70
+ // Attach shadow in closed mode to not have access except with the reference
71
+ this.shadowRoot = this.host.attachShadow({mode: 'closed'});
72
+ /*
73
+ * Block any click event that is not ins the shadow root
74
+ * This prevents an attacker to add element in the host and try to add event listener
75
+ */
76
+ this.host.addEventListener('click', event => {
77
+ if (!this.shadowRoot.contains(event.target)) {
78
+ event.stopImmediatePropagation(); // Block any external event
79
+ }
80
+ }, true); // Capture phase
81
+ // Insert the host in the body
82
+ document.body.appendChild(this.host);
83
+ }
84
+
44
85
  /**
45
86
  * Initializes the in-form manager
46
87
  */
47
88
  initialize() {
48
- this.clipboardServiceWorkerService = new ClipboardServiceWorkerService(port);
89
+ // Do not initialize if the page is not visible enough before inserting elements
90
+ if (this.isPageNotVisible()) {
91
+ console.debug("Cannot insert the in-form menu manager into a page that is not visible.");
92
+ return;
93
+ }
49
94
 
95
+ this.clipboardServiceWorkerService = new ClipboardServiceWorkerService(port);
96
+ this.createAndInsertShadowRootWithHost();
50
97
  this.findAndSetAuthenticationFields();
51
98
  this.handleDomChange();
52
99
  this.handleInformCallToActionRepositionEvent();
@@ -59,6 +106,7 @@ class InFormManager {
59
106
  this.handleFillCredentials();
60
107
  this.handleFillPassword();
61
108
  this.handleClipboardEvent();
109
+ this.handleDomStyleMutation();
62
110
  }
63
111
 
64
112
  /**
@@ -72,6 +120,52 @@ class InFormManager {
72
120
  this.handleClipboardChange = this.handleClipboardChange.bind(this);
73
121
  }
74
122
 
123
+ /**
124
+ * Monitor inline `style` attribute mutations on the host, <html>, and <body>.
125
+ * If a mutation makes any of these elements non-visible (e.g., display:none, opacity:0,
126
+ * visibility:hidden), the component is destroyed as a defensive measure.
127
+ */
128
+ handleDomStyleMutation() {
129
+ // Check any DOM style changes on the element
130
+ this.hostMutationObserver = new MutationObserver(() => this.destroyIfElementNotVisible(this.host));
131
+ this.htmlMutationObserver = new MutationObserver(() => this.destroyIfElementNotVisible(document.documentElement));
132
+ this.bodyMutationObserver = new MutationObserver(() => this.destroyIfElementNotVisible(document.body));
133
+
134
+ this.hostMutationObserver.observe(this.host, {attributes: true});
135
+ this.htmlMutationObserver.observe(document.documentElement, {attributes: true});
136
+ this.bodyMutationObserver.observe(document.body, {attributes: true});
137
+ }
138
+
139
+ /**
140
+ * Destroy all if element is not visible enough
141
+ * @param element
142
+ */
143
+ destroyIfElementNotVisible(element) {
144
+ if (this.isElementNotVisible(element)) {
145
+ this.destroy();
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Is element not visible
151
+ * @param element
152
+ * @return {boolean}
153
+ */
154
+ isElementNotVisible(element) {
155
+ const visibilityOptions = {
156
+ visibilityProperty: true
157
+ };
158
+ return getComputedStyle(element).opacity < 0.4 || !element.checkVisibility(visibilityOptions);
159
+ }
160
+
161
+ /**
162
+ * Is page not visible
163
+ * @return {boolean}
164
+ */
165
+ isPageNotVisible() {
166
+ return this.isElementNotVisible(document.documentElement) || this.isElementNotVisible(document.body);
167
+ }
168
+
75
169
  /**
76
170
  * Find authentication fields in the document and set them as object properties
77
171
  */
@@ -98,7 +192,7 @@ class InFormManager {
98
192
  const matchField = fieldToMatch => callToActionField => callToActionField.field === fieldToMatch;
99
193
  const existingField = this.callToActionFields.find(matchField(newField));
100
194
  const fieldType = newField.matches(InFormFieldSelector.USERNAME_FIELD_SELECTOR) ? 'username' : 'password';
101
- return existingField || new InFormCallToActionField(newField, fieldType);
195
+ return existingField || new InFormCallToActionField(newField, fieldType, this.shadowRoot);
102
196
  });
103
197
  } else {
104
198
  this.clean();
@@ -151,6 +245,15 @@ class InFormManager {
151
245
  */
152
246
  handleDomChange() {
153
247
  const updateAuthenticationFields = mutationsList => {
248
+ /*
249
+ * The only way to prevent an attacker trying to move the host into another parent element and add opacity
250
+ * If the host is not in the body anymore destroy
251
+ */
252
+ if (this.host.parentNode !== document.body) {
253
+ console.debug('Someone has moved the host of the shadow root');
254
+ this.destroy();
255
+ return;
256
+ }
154
257
  // Check if the mutation is an iframe added or removed by us
155
258
  const isMutationInformIframe = mutation => this.isInformIframe(mutation);
156
259
  // Check if our iframe is in the mutation list
@@ -203,7 +306,7 @@ class InFormManager {
203
306
  */
204
307
  handleInFormMenuInsertionEvent() {
205
308
  port.on('passbolt.in-form-menu.open', () => {
206
- this.menuField = new InFormMenuField(this.lastCallToActionFieldClicked.field);
309
+ this.menuField = new InFormMenuField(this.lastCallToActionFieldClicked.field, this.shadowRoot);
207
310
  });
208
311
  }
209
312
 
@@ -326,12 +429,16 @@ class InFormManager {
326
429
  */
327
430
  destroy() {
328
431
  this.mutationObserver.disconnect();
432
+ this.hostMutationObserver.disconnect();
433
+ this.htmlMutationObserver.disconnect();
434
+ this.bodyMutationObserver.disconnect();
329
435
  this.callToActionFields.forEach(field => field.destroy());
330
436
  this.menuField?.destroy();
331
437
  this.credentialsFormFields.forEach(field => field.destroy());
332
438
  window.removeEventListener('resize', this.clean);
333
439
  document.removeEventListener("cut", this.handleClipboardChange);
334
440
  document.removeEventListener("copy", this.handleClipboardChange);
441
+ this.host.remove();
335
442
  }
336
443
 
337
444
  /**
@@ -26,7 +26,8 @@ import {
26
26
  domElementLoginWithIdAttributeBenutzerkennung,
27
27
  domElementLoginWithIdAttributeBenutzername,
28
28
  domElementLoginWithIdAttributeEmail,
29
- domElementLoginWithIdAttributeLogin, domElementLoginWithIdAttributeLogto,
29
+ domElementLoginWithIdAttributeLogin,
30
+ domElementLoginWithIdAttributeLogto,
30
31
  domElementLoginWithIdAttributeUsername,
31
32
  domElementLoginWithNameAttributeBenutzerkennung,
32
33
  domElementLoginWithNameAttributeBenutzername,
@@ -75,6 +76,26 @@ describe("InformManager", () => {
75
76
  // mock port in window
76
77
  initializeWindow();
77
78
 
79
+ beforeEach(() => {
80
+ jest.clearAllMocks();
81
+ // Force to true as Jest do not provide opacity value
82
+ jest.spyOn(InFormManager, "isPageNotVisible").mockImplementation(() => false);
83
+ /** Mock create element to add a content window property in the iframe due to jest issue with iframe in shadow dom **/
84
+ const div = document.createElement("div");
85
+ const iframe = document.createElement("iframe");
86
+ jest.spyOn(document, "createElement").mockImplementation(elementName => {
87
+ if (elementName === "div") {
88
+ return div.cloneNode();
89
+ } else if (elementName === "iframe") {
90
+ const iframeMock = iframe.cloneNode();
91
+ Object.defineProperty(iframeMock, "contentWindow", {
92
+ value: {},
93
+ });
94
+ return iframeMock;
95
+ }
96
+ });
97
+ });
98
+
78
99
  afterEach(() => {
79
100
  InFormManager.destroy();
80
101
  });
@@ -1051,19 +1072,19 @@ describe("InformManager", () => {
1051
1072
  // eslint-disable-next-line no-unsanitized/property
1052
1073
  iframe.contentDocument.body.innerHTML = domElementLoginWithIdAttributeLogin;
1053
1074
  const informManager = new InformManagerPage();
1054
- expect(informManager.iframesLength).toBe(1);
1075
+ expect(informManager.iframesLength).toBe(0);
1055
1076
  await informManager.focusOnUsernameIframe();
1056
- expect(informManager.iframesLength).toBe(2);
1077
+ expect(informManager.iframesLength).toBe(1);
1057
1078
  await informManager.clickOnInformCallToAction();
1058
- expect(informManager.iframesLength).toBe(2);
1059
- await informManager.blurOnUsernameIframe();
1060
1079
  expect(informManager.iframesLength).toBe(1);
1080
+ await informManager.blurOnUsernameIframe();
1081
+ expect(informManager.iframesLength).toBe(0);
1061
1082
  await informManager.focusOnPasswordIframe();
1062
- expect(informManager.iframesLength).toBe(2);
1063
- await informManager.clickOnInformCallToAction(2);
1064
- expect(informManager.iframesLength).toBe(2);
1065
- await informManager.blurOnPasswordIframe();
1066
1083
  expect(informManager.iframesLength).toBe(1);
1084
+ await informManager.clickOnInformCallToAction(1);
1085
+ expect(informManager.iframesLength).toBe(1);
1086
+ await informManager.blurOnPasswordIframe();
1087
+ expect(informManager.iframesLength).toBe(0);
1067
1088
  });
1068
1089
 
1069
1090
  it("As LU I should destroy inform on port specific message", async() => {
@@ -1080,5 +1101,56 @@ describe("InformManager", () => {
1080
1101
  expect(InFormManager.destroy).toHaveBeenCalledTimes(1);
1081
1102
  expect(informManager.iframesLength).toBe(0);
1082
1103
  });
1104
+
1105
+ it("As LU I should destroy inform if opacity of the body change", async() => {
1106
+ expect.assertions(4);
1107
+ // Set up document body
1108
+ // eslint-disable-next-line no-unsanitized/property
1109
+ document.body.innerHTML = domElementLoginWithNameAttributeUsername; // The Dom
1110
+ jest.spyOn(InFormManager, 'destroy');
1111
+ const informManager = new InformManagerPage();
1112
+ expect(informManager.iframesLength).toBe(0);
1113
+ await informManager.focusOnUsername();
1114
+ expect(informManager.iframesLength).toBe(1);
1115
+ document.body.style.opacity = "0.3";
1116
+ await informManager.focusOnUsername();
1117
+
1118
+ expect(InFormManager.destroy).toHaveBeenCalledTimes(1);
1119
+ expect(informManager.iframesLength).toBe(0);
1120
+ });
1121
+
1122
+ it("As LU I should destroy inform if opacity of the html change", async() => {
1123
+ expect.assertions(4);
1124
+ // Set up document body
1125
+ // eslint-disable-next-line no-unsanitized/property
1126
+ document.body.innerHTML = domElementLoginWithNameAttributeUsername; // The Dom
1127
+ jest.spyOn(InFormManager, 'destroy');
1128
+ const informManager = new InformManagerPage();
1129
+ expect(informManager.iframesLength).toBe(0);
1130
+ await informManager.focusOnUsername();
1131
+ expect(informManager.iframesLength).toBe(1);
1132
+ document.documentElement.style.opacity = "0.3";
1133
+ await informManager.focusOnUsername();
1134
+
1135
+ expect(InFormManager.destroy).toHaveBeenCalledTimes(1);
1136
+ expect(informManager.iframesLength).toBe(0);
1137
+ });
1138
+
1139
+ it("As LU I should destroy inform if opacity of the host change", async() => {
1140
+ expect.assertions(4);
1141
+ // Set up document body
1142
+ // eslint-disable-next-line no-unsanitized/property
1143
+ document.body.innerHTML = domElementLoginWithNameAttributeUsername; // The Dom
1144
+ jest.spyOn(InFormManager, 'destroy');
1145
+ const informManager = new InformManagerPage();
1146
+ expect(informManager.iframesLength).toBe(0);
1147
+ await informManager.focusOnUsername();
1148
+ expect(informManager.iframesLength).toBe(1);
1149
+ InFormManager.host.setAttribute('style', 'opacity: 0.3 !important');
1150
+ await informManager.focusOnUsername();
1151
+
1152
+ expect(InFormManager.destroy).toHaveBeenCalledTimes(1);
1153
+ expect(informManager.iframesLength).toBe(0);
1154
+ });
1083
1155
  });
1084
1156
 
@@ -98,18 +98,11 @@ export default class InformManagerPage {
98
98
  return document.querySelector('[type=\"submit\"]');
99
99
  }
100
100
 
101
- /**
102
- * Returns the iframe call to action
103
- */
104
- get iframesCallToAction() {
105
- return document.querySelector('iframe');
106
- }
107
-
108
101
  /**
109
102
  * Returns the iframe length
110
103
  */
111
104
  get iframesLength() {
112
- return document.querySelectorAll('iframe').length;
105
+ return InFormManager.shadowRoot.querySelectorAll('iframe').length;
113
106
  }
114
107
 
115
108
  /** Blur on the username element */
@@ -23,8 +23,9 @@ class InFormMenuField {
23
23
  /**
24
24
  * Default constructor
25
25
  * @param field
26
+ * @param shadowRoot The shadow root
26
27
  */
27
- constructor(field) {
28
+ constructor(field, shadowRoot) {
28
29
  /** The field to which the in-form is attached */
29
30
  this.field = field;
30
31
  /** An unique identifier for the iframe */
@@ -34,6 +35,9 @@ class InFormMenuField {
34
35
  /** The scrollable field parent */
35
36
  this.scrollableFieldParent = null;
36
37
 
38
+
39
+ this.shadowRoot = shadowRoot;
40
+
37
41
  this.bindCallbacks();
38
42
  this.insertInformMenuIframe();
39
43
  this.handleRemoveEvent();
@@ -55,7 +59,7 @@ class InFormMenuField {
55
59
  * Insert an in-form menu iframe
56
60
  */
57
61
  async insertInformMenuIframe() {
58
- const iframes = document.querySelectorAll('iframe');
62
+ const iframes = this.shadowRoot.querySelectorAll('iframe');
59
63
  // Use of Array prototype some method cause NodeList is not an array !
60
64
  const iframeId = this.iframeId;
61
65
  const isIframeAlreadyInserted = Array.prototype.some.call(iframes, iframe => iframe.id === iframeId);
@@ -74,7 +78,7 @@ class InFormMenuField {
74
78
  const {top, left} = this.calculateIframePosition();
75
79
  const portId = await port.request("passbolt.port.generate-id", "InFormMenu");
76
80
  const iframe = document.createElement('iframe');
77
- document.body.appendChild(iframe);
81
+ this.shadowRoot.appendChild(iframe);
78
82
  const browserExtensionUrl = browser.runtime.getURL("/");
79
83
  iframe.id = this.iframeId;
80
84
  iframe.style.position = "fixed";
@@ -84,7 +88,6 @@ class InFormMenuField {
84
88
  iframe.style.border = "none";
85
89
  iframe.style.width = '370px'; // width of the menu 350px + 20px to display shadows
86
90
  iframe.style.height = '220px'; // For 3 items in a row to be display
87
- iframe.style.zIndex = "123456";
88
91
  iframe.style.colorScheme = "auto"; // To have the transparency on dark theme
89
92
  iframe.contentWindow.location = `${browserExtensionUrl}webAccessibleResources/passbolt-iframe-in-form-menu.html?passbolt=${portId}`;
90
93
  return iframe;
@@ -166,7 +169,7 @@ class InFormMenuField {
166
169
  * Remove the menu (iframe)
167
170
  */
168
171
  removeMenuIframe() {
169
- const iframes = document.querySelectorAll('iframe');
172
+ const iframes = this.shadowRoot.querySelectorAll('iframe');
170
173
  iframes.forEach(iframe => {
171
174
  const identifierToMatch = this.iframeId;
172
175
  if (iframe.id === identifierToMatch) {