Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 28 additions & 21 deletions packages/extension/src/services/dom/inputUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,21 @@ export async function inputContentEditable(
): Promise<boolean> {
if (!isEditable(el)) return false
el.focus()
const values = value.split("\n")

for (const [idx, val] of values.entries()) {
if (legacyMode) {
// Legacy mode when insertNode does not work correctly
// Used in perplexy.ai's chat input field
const values = value.split("\n")
if (legacyMode) {
// Use LegacyMode when range.insertNode is not reflected in a contentEditable element.
for (const [idx, val] of values.entries()) {
document.execCommand("insertText", false, val)
} else {
if (idx < values.length - 1) {
// For all but the last line, simulate Shift+Enter for line break
interval > 0 && (await sleep(interval / 2))
await typeShiftEnter(el)
interval > 0 && (await sleep(interval / 2))
}
}
} else {
for (const [idx, val] of values.entries()) {
const selection = window.getSelection()
if (selection) {
let range: Range
Expand Down Expand Up @@ -67,24 +74,24 @@ export async function inputContentEditable(
selection.removeAllRanges()
selection.addRange(range)
}
}

if (idx < values.length - 1) {
// For all but the last line, simulate Shift+Enter for line break
interval > 0 && (await sleep(interval / 2))
await typeShiftEnter(el)
interval > 0 && (await sleep(interval / 2))
if (idx < values.length - 1) {
// For all but the last line, simulate Shift+Enter for line break
interval > 0 && (await sleep(interval / 2))
await typeShiftEnter(el)
interval > 0 && (await sleep(interval / 2))
}
}
}

// Dispatch InputEvent to notify frameworks of the text change
const inputEvent = new InputEvent("input", {
inputType: "insertText",
data: value,
bubbles: true,
cancelable: false,
})
el.dispatchEvent(inputEvent)
// Dispatch InputEvent to notify frameworks of the text change
const inputEvent = new InputEvent("input", {
inputType: "insertText",
data: value,
bubbles: true,
cancelable: false,
})
el.dispatchEvent(inputEvent)
}
Comment on lines +86 to +94
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • InputEvent(insertText) のdispatchが else ブランチ内に移動しており、legacyMode=true の場合は insertText の InputEvent を送らなくなっています(以前は常にdispatchされていました)。
  • execCommand が常に期待通り input イベントを発火するとは限らないため、React/Lexical等のフレームワークが変更を検知できない可能性があります。
  • legacyModeでも従来通り insertText の InputEvent をdispatchする(条件分岐の外に戻す、または legacyMode 側でも明示的にdispatchする)形にしてください。

Copilot uses AI. Check for mistakes.

return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const mockIsEmpty = isEmpty as any

// Mock console methods
const mockConsole = {
warn: vi.spyOn(console, "warn").mockImplementation(() => {}),
warn: vi.spyOn(console, "warn").mockImplementation(() => { }),
}

// Mock DOM elements
Expand Down Expand Up @@ -919,7 +919,6 @@ describe("backgroundDispatcher", () => {
"test text",
1,
null,
false,
)
})

Expand Down Expand Up @@ -964,7 +963,6 @@ describe("backgroundDispatcher", () => {
"line1\nline2\nline3",
1,
null,
false,
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,7 @@ export const BackgroundPageActionDispatcher = {
element.dispatchEvent(inputEvent)
element.dispatchEvent(changeEvent)
} else if (element.isContentEditable) {
let legacyMode = false
if (location.href.includes("perplexity.ai")) {
// Legacy mode specifically for Perplexity.ai's contenteditable field.
// This is because it has some special handling that breaks the usual input simulation.
legacyMode = true
await inputContentEditable(element, "\n", 0, null, legacyMode)
}
await inputContentEditable(element, value, 1, null, legacyMode)
await inputContentEditable(element, value, 1, null)
}
}
} else {
Expand Down
15 changes: 4 additions & 11 deletions packages/extension/src/services/pageAction/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export namespace PageAction {

export type Click = {
type:
| PAGE_ACTION_EVENT.click
| PAGE_ACTION_EVENT.doubleClick
| PAGE_ACTION_EVENT.tripleClick
| PAGE_ACTION_EVENT.click
| PAGE_ACTION_EVENT.doubleClick
| PAGE_ACTION_EVENT.tripleClick
label: string
selector: string
selectorType: SelectorType
Expand Down Expand Up @@ -277,14 +277,7 @@ export const PageActionDispatcher = {
element.focus()
await user.type(element, value, { skipClick: true })
} else {
let legacyMode = false
if (location.href.includes("perplexity.ai")) {
// Legacy mode specifically for Perplexity.ai's contenteditable field.
// This is because it has some special handling that breaks the usual input simulation.
legacyMode = true
await inputContentEditable(element, "\n", 0, null, legacyMode)
}
await inputContentEditable(element, value, 40, null, legacyMode)
await inputContentEditable(element, value, 40, null)
}
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/hub/public/data/ai-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"div[contenteditable='true'].ProseMirror"
],
"submitSelectors": [
"#main-content button.Button_claude__c_hZy",
"#main-content button[data-state='closed']"
"#main-content button._claude_1abo4_159",
"#main-content button.can-focus:has(svg[viewBox='0 0 256 256'])"
]
},
{
Expand Down
4 changes: 2 additions & 2 deletions packages/hub/src/data/analytics.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"download": [
{
"eventId": "e024de34-3251-570d-9bd4-7eebb29d951d",
"eventCount": 83
"eventCount": 29
},
{
"eventId": "5bd801be-edf1-5787-91e0-48df29230fed",
Expand Down Expand Up @@ -424,4 +424,4 @@
}
],
"updated": 1775447960042
}
}
Loading