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.
- package/build/css/help.min.css +2 -2
- package/build/css/public.min.css +2 -2
- package/build/css/themes/default/api_authentication.min.css +2 -2
- package/build/css/themes/default/api_cloud.min.css +2 -2
- package/build/css/themes/default/api_main.min.css +2 -2
- package/build/css/themes/default/api_reports.min.css +2 -2
- package/build/css/themes/default/api_webinstaller.min.css +2 -2
- package/build/css/themes/default/ext_app.min.css +2 -2
- package/build/css/themes/default/ext_authentication.min.css +2 -2
- package/build/css/themes/default/ext_external.min.css +2 -2
- package/build/css/themes/default/ext_in_form_cta.min.css +2 -2
- package/build/css/themes/default/ext_in_form_menu.min.css +2 -2
- package/build/css/themes/default/ext_quickaccess.min.css +2 -2
- package/build/css/themes/midgar/api_authentication.min.css +2 -2
- package/build/css/themes/midgar/api_main.min.css +2 -2
- package/build/css/themes/midgar/api_reports.min.css +2 -2
- package/build/css/themes/midgar/ext_app.min.css +2 -2
- package/build/css/themes/midgar/ext_authentication.min.css +2 -2
- package/build/css/themes/midgar/ext_in_form_cta.min.css +2 -2
- package/build/css/themes/midgar/ext_in_form_menu.min.css +2 -2
- package/build/css/themes/midgar/ext_quickaccess.min.css +2 -2
- package/package.json +1 -1
- package/src/react-web-integration/lib/InForm/InFormCallToActionField.js +9 -7
- package/src/react-web-integration/lib/InForm/InFormManager.js +111 -4
- package/src/react-web-integration/lib/InForm/InformManager.test.js +81 -9
- package/src/react-web-integration/lib/InForm/InformManager.test.page.js +1 -8
- package/src/react-web-integration/lib/InForm/InformMenuField.js +8 -5
package/build/css/help.min.css
CHANGED
package/build/css/public.min.css
CHANGED
package/package.json
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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,
|
|
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(
|
|
1075
|
+
expect(informManager.iframesLength).toBe(0);
|
|
1055
1076
|
await informManager.focusOnUsernameIframe();
|
|
1056
|
-
expect(informManager.iframesLength).toBe(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
172
|
+
const iframes = this.shadowRoot.querySelectorAll('iframe');
|
|
170
173
|
iframes.forEach(iframe => {
|
|
171
174
|
const identifierToMatch = this.iframeId;
|
|
172
175
|
if (iframe.id === identifierToMatch) {
|