Skip to content

Commit 327f6e8

Browse files
Samiya CaurDevtools-frontend LUCI CQ
authored andcommitted
[AI Assistance] Temporary workaround for applying changes to shadow DOM elements
As part of setElementStyles, the styles are explicitly applied by updating root node's adopted stylesheets. Bug: 361746671 Fixed: 393267953 Change-Id: Id9c4d6d61dba0fb4f95a3c3119baa433207b191f Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6205049 Commit-Queue: Samiya Caur <[email protected]> Reviewed-by: Alex Rudenko <[email protected]>
1 parent c406636 commit 327f6e8

File tree

3 files changed

+86
-10
lines changed

3 files changed

+86
-10
lines changed

front_end/panels/ai_assistance/ChangeManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class ChangeManager {
8484
}
8585
}
8686

87-
async addChange(cssModel: SDK.CSSModel.CSSModel, frameId: Protocol.Page.FrameId, change: Change): Promise<void> {
87+
async addChange(cssModel: SDK.CSSModel.CSSModel, frameId: Protocol.Page.FrameId, change: Change): Promise<string> {
8888
const stylesheetId = await this.#getStylesheet(cssModel, frameId);
8989
const changes = this.#stylesheetChanges.get(stylesheetId) || [];
9090
const existingChange = changes.find(c => c.className === change.className);
@@ -101,6 +101,7 @@ export class ChangeManager {
101101
}
102102
await cssModel.setStyleSheetText(stylesheetId, this.buildChanges(changes), true);
103103
this.#stylesheetChanges.set(stylesheetId, changes);
104+
return this.buildChanges(changes);
104105
}
105106

106107
formatChanges(groupId: string): string {

front_end/panels/ai_assistance/ExtensionScope.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,13 @@ export class ExtensionScope {
147147
if (!cssModel) {
148148
throw new Error('CSSModel is not found');
149149
}
150-
await this.#changeManager.addChange(cssModel, this.frameId, {
150+
const styleChanges = await this.#changeManager.addChange(cssModel, this.frameId, {
151151
groupId: this.#agentId,
152152
selector,
153153
className,
154154
styles: arg.styles,
155155
});
156-
157-
await this.#simpleEval(executionContext, `freestyler.respond(${id})`);
156+
await this.#simpleEval(executionContext, `freestyler.respond(${id}, ${JSON.stringify(styleChanges)})`);
158157
});
159158
}
160159
}
@@ -182,8 +181,8 @@ const freestylerBinding = `if (!globalThis.freestyler) {
182181
freestyler.getArgs = (callbackId) => {
183182
return freestyler.callbacks.get(callbackId).args;
184183
}
185-
freestyler.respond = (callbackId) => {
186-
freestyler.callbacks.get(callbackId).resolver();
184+
freestyler.respond = (callbackId, styleChanges) => {
185+
freestyler.callbacks.get(callbackId).resolver(styleChanges);
187186
freestyler.callbacks.delete(callbackId);
188187
}
189188
}`;
@@ -219,10 +218,31 @@ const functions = `async function setElementStyles(el, styles) {
219218
el.style[key] = '';
220219
}
221220
222-
await freestyler({
221+
const result = await freestyler({
223222
method: 'setElementStyles',
224223
selector: selector,
225224
className,
226225
styles
227226
});
227+
228+
let rootNode = el.getRootNode();
229+
if (rootNode instanceof ShadowRoot) {
230+
let stylesheets = rootNode.adoptedStyleSheets;
231+
let hasAiStyleChange = false;
232+
let stylesheet = new CSSStyleSheet();
233+
for (let i = 0; i < stylesheets.length; i++) {
234+
const sheet = stylesheets[i];
235+
for (let j = 0; j < sheet.cssRules.length; j++) {
236+
hasAiStyleChange = sheet.cssRules[j].selectorText.startsWith('.${AI_ASSISTANCE_CSS_CLASS_NAME}');
237+
if (hasAiStyleChange) {
238+
stylesheet = sheet;
239+
break;
240+
}
241+
}
242+
}
243+
stylesheet.replaceSync(result);
244+
if (!hasAiStyleChange) {
245+
rootNode.adoptedStyleSheets = [...stylesheets, stylesheet];
246+
}
247+
}
228248
}`;

test/e2e/ai_assistance/ai_assistance_test.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ describe('AI Assistance', function() {
9090
}, messages);
9191
}
9292

93-
async function inspectNode(selector: string, iframeId?: string): Promise<void> {
93+
async function inspectNode(selector: string, iframeId?: string, shadowRoot?: string): Promise<void> {
9494
const {frontend} = getBrowserAndPages();
9595
await click(CONSOLE_TAB_SELECTOR);
9696
await frontend.locator('aria/Console prompt').click();
@@ -99,6 +99,10 @@ describe('AI Assistance', function() {
9999
inspectText = `inspect(document.querySelector('iframe#${iframeId}').contentDocument.querySelector((${
100100
JSON.stringify(selector)})))`;
101101
}
102+
if (shadowRoot) {
103+
inspectText = `inspect(document.querySelector(${JSON.stringify(shadowRoot)}).shadowRoot.querySelector((${
104+
JSON.stringify(selector)})))`;
105+
}
102106
await frontend.keyboard.type(inspectText);
103107
await frontend.keyboard.press('Enter');
104108
}
@@ -183,6 +187,7 @@ describe('AI Assistance', function() {
183187
resource?: string,
184188
node?: string,
185189
iframeId?: string,
190+
shadowRoot?: string,
186191
waitForSideEffect?: boolean,
187192
}) {
188193
const {
@@ -191,6 +196,7 @@ describe('AI Assistance', function() {
191196
resource = '../resources/recorder/recorder.html',
192197
node = 'div',
193198
iframeId,
199+
shadowRoot,
194200
waitForSideEffect
195201
} = options;
196202

@@ -211,6 +217,7 @@ describe('AI Assistance', function() {
211217
return await sendAiAssistanceMessage({
212218
node,
213219
iframeId,
220+
shadowRoot,
214221
query,
215222
messages,
216223
waitForSideEffect,
@@ -222,12 +229,13 @@ describe('AI Assistance', function() {
222229
messages: string[],
223230
node?: string,
224231
iframeId?: string,
232+
shadowRoot?: string,
225233
waitForSideEffect?: boolean,
226234
}) {
227-
const {messages, query, node = 'div', iframeId, waitForSideEffect} = options;
235+
const {messages, query, node = 'div', iframeId, shadowRoot, waitForSideEffect} = options;
228236

229237
await resetMockMessages(messages);
230-
await inspectNode(node, iframeId);
238+
await inspectNode(node, iframeId, shadowRoot);
231239
await typeQuery(query);
232240
return await submitAndWaitTillDone(waitForSideEffect);
233241
}
@@ -367,6 +375,53 @@ STOP`,
367375
});
368376
});
369377

378+
it('modifies multiple styles for elements inside shadow DOM', async () => {
379+
await runAiAssistance({
380+
query: 'Change the background color for this element to blue',
381+
messages: [
382+
`THOUGHT: I can change the background color of an element by setting the background-color CSS property.
383+
TITLE: changing the property
384+
ACTION
385+
await setElementStyles($0, { 'background-color': 'blue' });
386+
STOP`,
387+
'ANSWER: changed styles',
388+
],
389+
resource: '../resources/recorder/shadow-open.html',
390+
node: 'button',
391+
shadowRoot: 'login-element',
392+
});
393+
394+
const {target} = getBrowserAndPages();
395+
await target.bringToFront();
396+
await target.waitForFunction(() => {
397+
// @ts-ignore page context.
398+
return window.getComputedStyle(document.querySelector('login-element').shadowRoot.querySelector('button'))
399+
.backgroundColor === 'rgb(0, 0, 255)';
400+
});
401+
402+
await sendAiAssistanceMessage({
403+
query: 'Change the font color for this element to green',
404+
messages: [
405+
`THOUGHT: I can change the font color of an element by setting the color CSS property.
406+
TITLE: changing the property
407+
ACTION
408+
await setElementStyles($0, { 'color': 'green' });
409+
STOP`,
410+
'ANSWER: changed styles',
411+
],
412+
node: 'button',
413+
shadowRoot: 'login-element',
414+
});
415+
416+
await target.bringToFront();
417+
await target.waitForFunction(() => {
418+
const buttonStyles =
419+
// @ts-ignore page context.
420+
window.getComputedStyle(document.querySelector('login-element').shadowRoot.querySelector('button'));
421+
return buttonStyles.backgroundColor === 'rgb(0, 0, 255)' && buttonStyles.color === 'rgb(0, 128, 0)';
422+
});
423+
});
424+
370425
it('executes in the correct realm', async () => {
371426
const result = await runAiAssistance({
372427
query: 'What is the document title',

0 commit comments

Comments
 (0)