Compare commits

...

1 Commits

Author SHA1 Message Date
Samuel Maddock
52be29a601 feat: frame.copyImageAt 2025-01-21 11:46:16 -05:00
7 changed files with 90 additions and 8 deletions

View File

@@ -165,6 +165,13 @@ app.on('web-contents-created', (_, webContents) => {
})
```
#### `frame.copyImageAt(x, y)`
* `x` Integer
* `y` Integer
Copy the image at the given position to the clipboard.
### Instance Properties
#### `frame.ipc` _Readonly_

View File

@@ -476,6 +476,10 @@ WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event,
}
};
WebContents.prototype.copyImageAt = function (x: number, y: number) {
this.mainFrame.copyImageAt(x, y);
};
const addReplyToEvent = (event: Electron.IpcMainEvent) => {
const { processId, frameId } = event;
event.reply = (channel: string, ...args: any[]) => {

View File

@@ -3417,12 +3417,6 @@ void WebContents::ShowDefinitionForSelection() {
#endif
}
void WebContents::CopyImageAt(int x, int y) {
auto* const host = web_contents()->GetPrimaryMainFrame();
if (host)
host->CopyImageAt(x, y);
}
void WebContents::Focus() {
// Focusing on WebContents does not automatically focus the window on macOS
// and Linux, do it manually to match the behavior on Windows.
@@ -4538,7 +4532,6 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
.SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace)
.SetMethod("showDefinitionForSelection",
&WebContents::ShowDefinitionForSelection)
.SetMethod("copyImageAt", &WebContents::CopyImageAt)
.SetMethod("capturePage", &WebContents::CapturePage)
.SetMethod("setEmbedder", &WebContents::SetEmbedder)
.SetMethod("setDevToolsWebContents", &WebContents::SetDevToolsWebContents)

View File

@@ -282,7 +282,6 @@ class WebContents final : public ExclusiveAccessContext,
uint32_t FindInPage(gin::Arguments* args);
void StopFindInPage(content::StopFindAction action);
void ShowDefinitionForSelection();
void CopyImageAt(int x, int y);
// Focus.
void Focus();

View File

@@ -241,6 +241,12 @@ v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScript(
return handle;
}
void WebFrameMain::CopyImageAt(int x, int y) {
if (!CheckRenderFrame())
return;
return render_frame_->CopyImageAt(x, y);
}
bool WebFrameMain::Reload() {
if (!CheckRenderFrame())
return false;
@@ -520,6 +526,7 @@ void WebFrameMain::FillObjectTemplate(v8::Isolate* isolate,
.SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript)
.SetMethod("collectJavaScriptCallStack",
&WebFrameMain::CollectDocumentJSCallStack)
.SetMethod("copyImageAt", &WebFrameMain::CopyImageAt)
.SetMethod("reload", &WebFrameMain::Reload)
.SetMethod("isDestroyed", &WebFrameMain::IsDestroyed)
.SetMethod("_send", &WebFrameMain::Send)

View File

@@ -107,6 +107,7 @@ class WebFrameMain final : public gin::Wrappable<WebFrameMain>,
v8::Local<v8::Promise> ExecuteJavaScript(gin::Arguments* args,
const std::u16string& code);
void CopyImageAt(int x, int y);
bool Reload();
bool IsDestroyed() const;
void Send(v8::Isolate* isolate,

View File

@@ -1,3 +1,4 @@
import { clipboard } from 'electron';
import { BrowserWindow, WebFrameMain, webFrameMain, ipcMain, app, WebContents } from 'electron/main';
import { expect } from 'chai';
@@ -475,6 +476,76 @@ describe('webFrameMain module', () => {
});
});
describe('webFrameMain.copyImageAt', () => {
const insertImageInFrame = async (frame: WebFrameMain) => {
const imgPath = path.join(fixtures, 'assets', 'capybara.png');
const imgSrc = url.pathToFileURL(imgPath);
await frame.executeJavaScript(`(${(src: string) => {
return new Promise((resolve) => {
const img = document.createElement('img');
img.onload = resolve;
img.src = src;
document.body.appendChild(img);
});
}})(${JSON.stringify(imgSrc)})`);
};
const getFramePosition = async (frame: WebFrameMain) => {
const point = await frame.executeJavaScript(`(${() => {
const iframe = document.querySelector('iframe');
if (!iframe) return;
const rect = iframe.getBoundingClientRect();
return { x: Math.floor(rect.x), y: Math.floor(rect.y) };
}})()`) as Electron.Point;
expect(point).to.be.an('object');
return point;
};
const copyImageInFrame = async (frame: WebFrameMain) => {
const point = await frame.executeJavaScript(`(${() => {
const img = document.querySelector('img');
if (!img) return;
const rect = img.getBoundingClientRect();
return {
x: Math.floor(rect.x + rect.width / 2),
y: Math.floor(rect.y + rect.height / 2)
};
}})()`) as Electron.Point;
expect(point).to.be.an('object');
// Translate coordinate to be relative of main frame
if (frame.parent) {
const framePosition = await getFramePosition(frame.parent);
point.x += framePosition.x;
point.y += framePosition.y;
}
frame.copyImageAt(point.x, point.y);
};
beforeEach(() => {
clipboard.clear();
});
it('copies image in main frame', async () => {
const w = new BrowserWindow({ show: false });
await w.webContents.loadFile(path.join(fixtures, 'blank.html'));
await insertImageInFrame(w.webContents.mainFrame);
await copyImageInFrame(w.webContents.mainFrame);
await waitUntil(() => clipboard.availableFormats().includes('image/png'));
});
it('copies image in subframe', async () => {
const w = new BrowserWindow({ show: false });
await w.webContents.loadFile(path.join(subframesPath, 'frame-with-frame.html'));
const subframe = w.webContents.mainFrame.frames[0];
expect(subframe).to.exist();
await insertImageInFrame(subframe);
await copyImageInFrame(subframe);
await waitUntil(() => clipboard.availableFormats().includes('image/png'));
});
});
describe('"frame-created" event', () => {
it('emits when the main frame is created', async () => {
const w = new BrowserWindow({ show: false });