I’m building a Chrome extension to automate solution post creation on LeetCode after a solution is accepted. The goal is to:1. Open the “Post Solution” page.2. Automatically populate the “Title” input field with a custom string.3. Populate the Monaco Editor with a formatted solution template.
The Setup
1. Environment:
• Chrome Extension (Manifest V3) using Plasmo Framework.
• The extension includes:
• A background script to handle tab creation and messaging.
• A content script for DOM interaction on the “Post Solution” page.
2. Approach:
• The background script opens the solution post page and sends a message to the content script with the template to paste.
• The content script handles DOM manipulation, including:
• Typing text into the input field.
• Copying and pasting content into the Monaco Editor.
Challenges and What We’ve Tried
- Populating the Title Input Field
Problem:
The input field sometimes partially updates (e.g., “Automated Title” becomes “tomated Title”) or doesn’t update at all.
Attempts:
1. Directly setting value:
inputElement.value = "Automated Title";
inputElement.dispatchEvent(new Event("input", { bubbles: true }));
inputElement.dispatchEvent(new Event("change", { bubbles: true }));
•Result: Sometimes works, but often doesn’t update the field properly or triggers listeners that reset the value.
2.Simulating Keypress Events:
for (const char of text) {
inputElement.value += char;
inputElement.dispatchEvent(new KeyboardEvent("keydown", { key: char }));
inputElement.dispatchEvent(new KeyboardEvent("keypress", { key: char }));
inputElement.dispatchEvent(new KeyboardEvent("keyup", { key: char }));
}
•Result: This works better but occasionally results in incomplete text. It seems that external event listeners or DOM updates interfere with the process.
- Copying Text to Clipboard and Pasting:
await navigator.clipboard.writeText("Automated Title");
const pasteEvent = new ClipboardEvent("paste", { bubbles: true });
inputElement.dispatchEvent(pasteEvent);
•Result: The clipboard API fails with a NotAllowedError unless triggered by a user interaction (e.g., button click).
- Populating the Monaco Editor
Problem:
The Monaco Editor relies on its monaco.editor.getModels()[0] API, which isn’t directly accessible from the content script due to the script running in a different execution context.
Attempts:
1. Using Monaco API:
const model = monaco.editor.getModels()[0];
model.setValue(template);
•Result: Works perfectly in the browser console but fails in the content script with monaco is undefined.
2.Injecting Code into Main World:
• Injected a function using chrome.scripting.executeScript:
chrome.scripting.executeScript({
target: { tabId: tab.id },
world: "MAIN",
func: pasteTemplateIntoPage,
args: [template],
});
•Result: monaco is undefined in the main world due to CSP restrictions.
- Handling Rate Limiting (429 Errors)
Problem:
Occasionally, setting the title triggers LeetCode’s backend requests that result in 429 Too Many Requests.
Attempts:
1. Added delays between actions.
2. Reduced unnecessary event dispatching.
3. Used debounce to avoid rapid interactions.
•Result: Rate limiting persists when actions are too frequent.
Code Snippets
Background Script:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "openSubmissionPage") {
chrome.tabs.create({ url: message.url, active: true }, (tab) => {
if (tab?.id) {
chrome.tabs.onUpdated.addListener(function listener(tabId, changeInfo) {
if (tabId === tab.id && changeInfo.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
chrome.tabs.sendMessage(tab.id, { action: "pasteTemplate", template: message.template });
}
});
}
});
Content Script:
async function typeIntoInputField(selector, text) {
const inputElement = document.querySelector(selector);
if (!inputElement) return;
inputElement.value = "";
for (const char of text) {
inputElement.value += char;
inputElement.dispatchEvent(new KeyboardEvent("keydown", { key: char }));
inputElement.dispatchEvent(new KeyboardEvent("keypress", { key: char }));
inputElement.dispatchEvent(new KeyboardEvent("keyup", { key: char }));
await new Promise((r) => setTimeout(r, 100));
}
}
What We Need Help With:
1. How can we reliably set the title input field’s value (e.g., “Automated Title”) while avoiding partial updates or resets?
2. What’s the best way to access and manipulate the Monaco Editor in a Chrome extension?
3. How can we avoid triggering LeetCode’s rate limiting (429 errors) during DOM manipulation?
4. Are there other robust methods to simulate user interaction (typing, pasting) programmatically?
What We’ve Tried:
• Direct DOM manipulation (value, setAttribute).
• Simulating keystrokes using KeyboardEvent.
• Copying to clipboard and pasting using ClipboardEvent.
• Injecting code into the main world to access the Monaco API.
Despite all these attempts, I still face issues with reliability and compatibility. Any advice, insights, or alternative approaches would be greatly appreciated!
Feel free to copy and paste this plain-text version into Stack Overflow!