Skip to content

Commit 9b1b59b

Browse files
YahorCYahor ChaptsouYahor Chaptsou
authored
[SITES-41320] improve imagev2 performance (#3024)
* [SITES-41320] improve imagev2 perfromance * [SITES-41279] improve imagev3 perfromance * [SITES-41279] image v3 perfromance improvment * [SITES-41279] image v3 perfromance improvment * [SITES-41279] image v3 perfromance improvment * [SITES-41279] image v3 perfromance improvment * [SITES-41279] image v3 perfromance improvment * [SITES-41279] image v3 perfromance improvment * [SITES-41320] image v2 perfromance improve * [SITES-41320] image v2 perfromance improve * [SITES-41320] image v2 perfromance improve * [SITES-41320] image v2 perfromance improve * [SITES-41320] image v2 perfromance improve * [SITES-41320] image v2 perfromance improve --------- Co-authored-by: Yahor Chaptsou <yahorchaptsou@Yahors-MacBook-Pro.local> Co-authored-by: Yahor Chaptsou <yahorchaptsou@Yahors-MBP.home>
1 parent 396eb12 commit 9b1b59b

7 files changed

Lines changed: 283 additions & 18 deletions

File tree

content/karma.conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module.exports = function(config) {
4343
},
4444
'src/content/jcr_root/apps/core/wcm/components/commons/editor/clientlibs/htmlidvalidator/js/*.js',
4545
'src/content/jcr_root/apps/core/wcm/components/commons/editor/clientlibs/authoringutils/js/*.js',
46+
'src/content/jcr_root/apps/core/wcm/components/image/v2/image/clientlibs/editor/js/image.js',
4647
'src/content/jcr_root/apps/core/wcm/components/image/v3/image/clientlibs/editor/js/image.js',
4748
'test/**/*Test.js',
4849
'test/**/*Test.html'

content/src/content/jcr_root/apps/core/wcm/components/image/v2/image/clientlibs/editor/.content.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
33
jcr:primaryType="cq:ClientLibraryFolder"
44
categories="[core.wcm.components.image.v2.editor]"
5-
dependencies="[jquery,core.wcm.components.commons.v1.editor.checkboxTextfieldTuple]"/>
5+
dependencies="[jquery,core.wcm.components.commons.v1.editor.checkboxTextfieldTuple,core.wcm.components.commons.editor.authoringutils]"/>

content/src/content/jcr_root/apps/core/wcm/components/image/v2/image/clientlibs/editor/js/image.js

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,73 @@
4343
var smartCropRenditionFromJcr;
4444
var smartCropRenditionsDropDown;
4545

46+
var CT_SITES_41320 = "CT_SITES-41320";
47+
/*
48+
* Granite feature toggle CT_SITES_41320 gates Coral label preparation and Dynamic Media request URL checks
49+
* in the Image v2 author dialog. When Granite or the toggle API is missing, helpers behave as enabled; set the
50+
* toggle to false only to restore the previous Image v2 dialog behaviour.
51+
*/
52+
53+
/**
54+
* Whether Coral smart-crop labels and Dynamic Media URL handling use commons authoring helpers.
55+
* When Granite reports CT_SITES_41320 as disabled, the dialog matches earlier Image v2 behaviour.
56+
*
57+
* @returns {Boolean} true when helpers apply, false when the toggle explicitly disables them
58+
*/
59+
function isImageV2AuthoringMarkupHelpersEnabled() {
60+
if (!Granite || !Granite.Toggles || typeof Granite.Toggles.isEnabled !== "function") {
61+
return true;
62+
}
63+
return Granite.Toggles.isEnabled(CT_SITES_41320) !== false;
64+
}
65+
66+
function getImageAuthoringUtils() {
67+
return window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.AuthoringEditorUtils && window.CQ.CoreComponents.AuthoringEditorUtils.image;
68+
}
69+
70+
/**
71+
* Label HTML for smart crop dropdown items; with CT_SITES_41320, values are prepared for Coral innerHTML rendering.
72+
*
73+
* @param {*} value smart crop label or related value
74+
* @returns {String} label text or HTML-safe markup suitable for Coral rendering when CT_SITES_41320 applies
75+
*/
76+
function formatSmartCropOptionLabel(value) {
77+
if (isImageV2AuthoringMarkupHelpersEnabled()) {
78+
var utils = getImageAuthoringUtils();
79+
if (!utils) {
80+
return String(value == null ? "" : value);
81+
}
82+
if (typeof utils.formatPlainTextForMarkup !== "function") {
83+
return String(value == null ? "" : value);
84+
}
85+
return utils.formatPlainTextForMarkup(value);
86+
}
87+
if (value === undefined || value === null) {
88+
return "";
89+
}
90+
return String(value);
91+
}
92+
93+
/**
94+
* Whether dam:scene7File can drive image service requests; with CT_SITES_41320, repository path rules from commons apply.
95+
*
96+
* @param {*} path dam:scene7File or equivalent metadata path
97+
* @returns {Boolean} whether the path is allowed for scene7-style requests when CT_SITES_41320 applies
98+
*/
99+
function isDamScene7FileEligible(path) {
100+
if (isImageV2AuthoringMarkupHelpersEnabled()) {
101+
var utils = getImageAuthoringUtils();
102+
if (!utils) {
103+
return true;
104+
}
105+
if (typeof utils.isDamScene7PathEligible !== "function") {
106+
return true;
107+
}
108+
return utils.isDamScene7PathEligible(path);
109+
}
110+
return true;
111+
}
112+
46113
$(document).on("dialog-loaded", function(e) {
47114
var $dialog = e.dialog;
48115
var $dialogContent = $dialog.find(dialogContentSelector);
@@ -220,7 +287,12 @@
220287
$dynamicMediaGroup.hide();
221288
} else {
222289
$dynamicMediaGroup.show();
223-
getSmartCropRenditions(data["dam:scene7File"]);
290+
if (isDamScene7FileEligible(data["dam:scene7File"])) {
291+
getSmartCropRenditions(data["dam:scene7File"]);
292+
} else {
293+
$dynamicMediaGroup.find(presetTypeSelector).parent().hide();
294+
selectPresetType($(presetTypeSelector), "imagePreset");
295+
}
224296
}
225297
}
226298
});
@@ -247,11 +319,23 @@
247319
* @param {String} imageUrl The link to image asset
248320
*/
249321
function getSmartCropRenditions(imageUrl) {
322+
if (!isDamScene7FileEligible(imageUrl)) {
323+
return;
324+
}
250325
if (imagePropertiesRequest) {
251326
imagePropertiesRequest.abort();
252327
}
253328
imagePropertiesRequest = new XMLHttpRequest();
254329
var url = window.location.origin + "/is/image/" + imageUrl + "?req=set,json";
330+
if (isImageV2AuthoringMarkupHelpersEnabled()) {
331+
try {
332+
if (new URL(url).origin !== window.location.origin) {
333+
return;
334+
}
335+
} catch (err) {
336+
return;
337+
}
338+
}
255339
imagePropertiesRequest.open("GET", url, true);
256340
imagePropertiesRequest.onload = function() {
257341
if (imagePropertiesRequest.status >= 200 && imagePropertiesRequest.status < 400) {
@@ -278,12 +362,14 @@
278362
// "AUTO" would trigger automatic smart crop operation; also we need to check "AUTO" was chosed in previous session
279363
addSmartCropDropDownItem("Auto", "SmartCrop:Auto", (smartCropRenditionFromJcr === "SmartCrop:Auto"));
280364
for (var i = 0; i < payload.set.relation.length; i++) {
365+
var userdata = payload.set.relation[i].userdata || {};
366+
var smartCropDef = userdata.SmartCropDef;
281367
smartCropRenditionsDropDown.items.add({
282368
content: {
283-
innerHTML: payload.set.relation[i].userdata.SmartCropDef
369+
innerHTML: formatSmartCropOptionLabel(smartCropDef)
284370
},
285371
disabled: false,
286-
selected: (smartCropRenditionFromJcr === payload.set.relation[i].userdata.SmartCropDef)
372+
selected: (smartCropRenditionFromJcr === smartCropDef)
287373
});
288374
}
289375
$dynamicMediaGroup.find(presetTypeSelector).parent().show();
@@ -308,7 +394,7 @@
308394
function addSmartCropDropDownItem(label, value, selected) {
309395
smartCropRenditionsDropDown.items.add({
310396
content: {
311-
innerHTML: label,
397+
innerHTML: formatSmartCropOptionLabel(label),
312398
value: value
313399
},
314400
disabled: false,
@@ -441,4 +527,14 @@
441527
}
442528
}
443529

530+
var imageV2EditorTestApiHost = typeof globalThis === "undefined" ? window : globalThis;
531+
532+
/* Karma (mocks.js) sets globalThis.__IMAGE_V2_EDITOR_TEST_API; AEM runtime leaves it undefined. */
533+
if (imageV2EditorTestApiHost.__IMAGE_V2_EDITOR_TEST_API) {
534+
imageV2EditorTestApiHost.__IMAGE_V2_EDITOR_TEST_API.formatSmartCropOptionLabel = formatSmartCropOptionLabel;
535+
imageV2EditorTestApiHost.__IMAGE_V2_EDITOR_TEST_API.isDamScene7FileEligible = isDamScene7FileEligible;
536+
imageV2EditorTestApiHost.__IMAGE_V2_EDITOR_TEST_API.isImageV2AuthoringMarkupHelpersEnabled = isImageV2AuthoringMarkupHelpersEnabled;
537+
imageV2EditorTestApiHost.__IMAGE_V2_EDITOR_TEST_API.getImageAuthoringUtils = getImageAuthoringUtils;
538+
}
539+
444540
})(jQuery, Granite);

content/src/content/jcr_root/apps/core/wcm/components/image/v3/image/clientlibs/editor/js/image.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@
6666
var remoteFileReference;
6767
var dataSeededValueAttr = "data-seeded-value";
6868

69-
var FT_SITES_41279 = "FT_SITES-41279";
69+
var CT_SITES_41279 = "CT_SITES-41279";
7070
/*
71-
* Granite feature toggle FT_SITES-41279 gates Image v3 editor markup helpers (Coral crop labels, Dynamic Media path checks,
71+
* Granite feature toggle CT_SITES-41279 gates Image v3 editor markup helpers (Coral crop labels, Dynamic Media path checks,
7272
* page-image thumbnail shell import). Helpers stay on unless the toggle is explicitly false—use false only as a short
7373
* operator rollback while investigating regressions; Cloud environments should ship with this enabled.
7474
*/
@@ -77,18 +77,18 @@
7777
if (!Granite || !Granite.Toggles || typeof Granite.Toggles.isEnabled !== "function") {
7878
return true;
7979
}
80-
return Granite.Toggles.isEnabled(FT_SITES_41279) !== false;
80+
return Granite.Toggles.isEnabled(CT_SITES_41279) !== false;
8181
}
8282

8383
function getImageAuthoringUtils() {
8484
return window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.AuthoringEditorUtils && window.CQ.CoreComponents.AuthoringEditorUtils.image;
8585
}
8686

8787
/**
88-
* Label HTML for smart crop dropdown items; with FT_SITES-41279, values are prepared for Coral innerHTML rendering.
88+
* Label HTML for smart crop dropdown items; with CT_SITES-41279, values are prepared for Coral innerHTML rendering.
8989
*
9090
* @param {*} value smart crop label or related value
91-
* @returns {String} label text or HTML-safe markup suitable for Coral rendering when FT_SITES-41279 applies
91+
* @returns {String} label text or HTML-safe markup suitable for Coral rendering when CT_SITES-41279 applies
9292
*/
9393
function formatSmartCropOptionLabel(value) {
9494
if (isImageV3AuthoringMarkupHelpersEnabled()) {
@@ -108,10 +108,10 @@
108108
}
109109

110110
/**
111-
* Whether dam:scene7File can drive image service requests; with FT_SITES-41279, repository path rules from commons apply.
111+
* Whether dam:scene7File can drive image service requests; with CT_SITES-41279, repository path rules from commons apply.
112112
*
113113
* @param {*} path dam:scene7File or equivalent metadata path
114-
* @returns {Boolean} whether the path is allowed for scene7-style requests when FT_SITES-41279 applies
114+
* @returns {Boolean} whether the path is allowed for scene7-style requests when CT_SITES-41279 applies
115115
*/
116116
function isDamScene7FileEligible(path) {
117117
if (isImageV3AuthoringMarkupHelpersEnabled()) {
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*******************************************************************************
2+
* Copyright 2026 Adobe
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
******************************************************************************/
16+
/**
17+
* Covers Image v2 editor {@code image.js} (CT_SITES-41320) and related {@code AuthoringEditorUtils.image} checks
18+
* wired through {@code globalThis.__IMAGE_V2_EDITOR_TEST_API}. Karma loads {@code image/v2/.../image.js} after {@code authoringutils}.
19+
*/
20+
function imageV2EditorImageTestFtOn() {
21+
globalThis.Granite.Toggles.isEnabled = function() {
22+
return true;
23+
};
24+
}
25+
26+
function imageV2EditorImageTestFtOff() {
27+
globalThis.Granite.Toggles.isEnabled = function(key) {
28+
return key !== "CT_SITES-41320";
29+
};
30+
}
31+
32+
describe("Image v2 editor image.js (Karma-loaded)", function() {
33+
let api;
34+
let imageUtils;
35+
let togglesIsEnabled;
36+
37+
beforeAll(function() {
38+
api = globalThis.__IMAGE_V2_EDITOR_TEST_API;
39+
imageUtils = globalThis.CQ.CoreComponents.AuthoringEditorUtils.image;
40+
togglesIsEnabled = globalThis.Granite.Toggles.isEnabled;
41+
});
42+
43+
afterEach(function() {
44+
globalThis.Granite.Toggles.isEnabled = togglesIsEnabled;
45+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = imageUtils;
46+
});
47+
48+
describe("__IMAGE_V2_EDITOR_TEST_API", function() {
49+
it("exposes helpers used by the smart crop and Dynamic Media dialog paths", function() {
50+
expect(api).toBeDefined();
51+
expect(typeof api.formatSmartCropOptionLabel).toBe("function");
52+
expect(typeof api.isDamScene7FileEligible).toBe("function");
53+
expect(typeof api.isImageV2AuthoringMarkupHelpersEnabled).toBe("function");
54+
expect(typeof api.getImageAuthoringUtils).toBe("function");
55+
});
56+
57+
it("getImageAuthoringUtils returns AuthoringEditorUtils.image when loaded", function() {
58+
expect(api.getImageAuthoringUtils()).toBe(globalThis.CQ.CoreComponents.AuthoringEditorUtils.image);
59+
});
60+
});
61+
62+
describe("isImageV2AuthoringMarkupHelpersEnabled (Granite toggle)", function() {
63+
it("treats missing Granite.Toggles as enabled", function() {
64+
const saved = globalThis.Granite.Toggles;
65+
globalThis.Granite.Toggles = undefined;
66+
expect(api.isImageV2AuthoringMarkupHelpersEnabled()).toBe(true);
67+
globalThis.Granite.Toggles = saved;
68+
});
69+
70+
it("returns false when CT_SITES-41320 is explicitly disabled", function() {
71+
globalThis.Granite.Toggles.isEnabled = function(key) {
72+
return key !== "CT_SITES-41320";
73+
};
74+
expect(api.isImageV2AuthoringMarkupHelpersEnabled()).toBe(false);
75+
});
76+
77+
it("returns true when CT_SITES-41320 is enabled", function() {
78+
imageV2EditorImageTestFtOn();
79+
expect(api.isImageV2AuthoringMarkupHelpersEnabled()).toBe(true);
80+
});
81+
});
82+
83+
describe("formatSmartCropOptionLabel", function() {
84+
it("keeps legacy string behaviour when FT is off", function() {
85+
imageV2EditorImageTestFtOff();
86+
expect(api.formatSmartCropOptionLabel("a<b>c")).toBe("a<b>c");
87+
expect(api.formatSmartCropOptionLabel(null)).toBe("");
88+
expect(api.formatSmartCropOptionLabel(undefined)).toBe("");
89+
});
90+
91+
it("delegates to AuthoringEditorUtils.image when FT is on", function() {
92+
imageV2EditorImageTestFtOn();
93+
const html = api.formatSmartCropOptionLabel("x < y");
94+
expect(html.indexOf("<")).toBe(-1);
95+
});
96+
97+
it("encodes crop names that contain script-like markup when FT is on", function() {
98+
imageV2EditorImageTestFtOn();
99+
const encoded = api.formatSmartCropOptionLabel('SmartCrop<script>alert(1)</script>');
100+
expect(encoded.indexOf("<script>")).toBe(-1);
101+
expect(encoded.indexOf("SmartCrop")).not.toBe(-1);
102+
});
103+
104+
it("encodes angle brackets for arbitrary label strings when FT is on", function() {
105+
imageV2EditorImageTestFtOn();
106+
const html = api.formatSmartCropOptionLabel("16:9 <extra>");
107+
expect(html.indexOf("<")).toBe(-1);
108+
expect(html.indexOf("16:9")).not.toBe(-1);
109+
});
110+
111+
it("falls back when FT is on but image utils are missing", function() {
112+
imageV2EditorImageTestFtOn();
113+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = undefined;
114+
expect(api.formatSmartCropOptionLabel("plain")).toBe("plain");
115+
expect(api.formatSmartCropOptionLabel(null)).toBe("");
116+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = imageUtils;
117+
});
118+
119+
it("falls back when FT is on but formatPlainTextForMarkup is not a function", function() {
120+
imageV2EditorImageTestFtOn();
121+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = { isDamScene7PathEligible: imageUtils.isDamScene7PathEligible };
122+
expect(api.formatSmartCropOptionLabel("x")).toBe("x");
123+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = imageUtils;
124+
});
125+
});
126+
127+
describe("isDamScene7FileEligible", function() {
128+
it("is permissive when FT is off", function() {
129+
imageV2EditorImageTestFtOff();
130+
expect(api.isDamScene7FileEligible("javascript:alert(1)")).toBe(true);
131+
expect(api.isDamScene7FileEligible("data:text/html,x")).toBe(true);
132+
});
133+
134+
it("delegates when FT is on", function() {
135+
imageV2EditorImageTestFtOn();
136+
expect(api.isDamScene7FileEligible("/content/dam/x")).toBe(true);
137+
expect(api.isDamScene7FileEligible("javascript:alert(1)")).toBe(false);
138+
expect(api.isDamScene7FileEligible("data:text/html,<x>")).toBe(false);
139+
expect(api.isDamScene7FileEligible("JaVaScRiPt:x")).toBe(false);
140+
});
141+
142+
it("rejects unstable path segments when FT is on", function() {
143+
imageV2EditorImageTestFtOn();
144+
expect(api.isDamScene7FileEligible("../../etc/passwd")).toBe(false);
145+
});
146+
147+
it("returns true when FT is on but image utils are missing", function() {
148+
imageV2EditorImageTestFtOn();
149+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = undefined;
150+
expect(api.isDamScene7FileEligible("javascript:x")).toBe(true);
151+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = imageUtils;
152+
});
153+
154+
it("returns true when FT is on but isDamScene7PathEligible is not a function", function() {
155+
imageV2EditorImageTestFtOn();
156+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = { formatPlainTextForMarkup: imageUtils.formatPlainTextForMarkup };
157+
expect(api.isDamScene7FileEligible("/content/dam/x")).toBe(true);
158+
globalThis.CQ.CoreComponents.AuthoringEditorUtils.image = imageUtils;
159+
});
160+
});
161+
162+
describe("AuthoringEditorUtils.image path rules (metadata-style references)", function() {
163+
it("returns false when a value walks outside a stable repository path", function() {
164+
expect(imageUtils.isDamScene7PathEligible("../../content/usergenerated/demo/payload/asset.json?")).toBe(false);
165+
});
166+
});
167+
});

0 commit comments

Comments
 (0)