clipboard 剪切板js chrom参考
clipboard api参考:
https://www.inovex.de/blog/clipboard-api/
https://w3c.github.io/clipboard-apis/
mdn的文档:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
js promise详解:https://www.jb51.net/article/139825.htm
有深度的示例:http://help.dottoro.com/ljwexqxl.php
clipboard js api实现
js示例代码:
navigator.clipboard.writeText(info).then(function() { /* clipboard successfully set */ showMsg("Success to write clipboard:"+info) }, function() { showMsg("Fail to write clipboard!") /* clipboard write failed */ });
由idl自动生成的绑定代码进入:
自动生成的绑定代码:v8_clipboard.cc
void V8Clipboard::WriteTextMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_Clipboard_writeText"); ExecutionContext* execution_context_for_measurement = CurrentExecutionContext(info.GetIsolate()); UseCounter::Count(execution_context_for_measurement, WebFeature::kAsyncClipboardAPIWriteText); clipboard_v8_internal::WriteTextMethod(info); } static void WriteTextMethod(const v8::FunctionCallbackInfo<v8::Value>& info) { ExceptionState exception_state(info.GetIsolate(), ExceptionState::kExecutionContext, "Clipboard", "writeText"); ExceptionToRejectPromiseScope reject_promise_scope(info, exception_state); // V8DOMConfiguration::kDoNotCheckHolder // Make sure that info.Holder() really points to an instance of the type. if (!V8Clipboard::HasInstance(info.Holder(), info.GetIsolate())) { exception_state.ThrowTypeError("Illegal invocation"); return; } Clipboard* impl = V8Clipboard::ToImpl(info.Holder()); ScriptState* script_state = ScriptState::ForRelevantRealm(info); if (UNLIKELY(info.Length() < 1)) { exception_state.ThrowTypeError(ExceptionMessages::NotEnoughArguments(1, info.Length())); return; } V8StringResource<> data; data = info[0]; if (!data.Prepare(exception_state)) return; ScriptPromise result = impl->writeText(script_state, data); V8SetReturnValue(info, result.V8Value()); }
- idl third_party\blink\renderer\modules\clipboard\clipboard.idl:
[ SecureContext, Exposed=Window ] interface Clipboard : EventTarget { [MeasureAs=AsyncClipboardAPIRead, CallWith=ScriptState, RuntimeEnabled=AsyncClipboard ] Promise<sequence<ClipboardItem>> read(); [MeasureAs=AsyncClipboardAPIReadText, CallWith=ScriptState ] Promise<DOMString> readText(); [MeasureAs=AsyncClipboardAPIWrite, CallWith=ScriptState, RuntimeEnabled=AsyncClipboard ] Promise<void> write(sequence<ClipboardItem> data); [MeasureAs=AsyncClipboardAPIWriteText, CallWith=ScriptState ] Promise<void> writeText(DOMString data); };
2,cpp实现
clipboard.cc clipboard.h
通过v8 bind,调用到。比如writeText()
3,clipboard_promise.cc 实现了js promise
ScriptPromise ClipboardPromise::CreateForWriteText(ScriptState* script_state, const String& data) { ClipboardPromise* clipboard_promise = MakeGarbageCollected<ClipboardPromise>(script_state); clipboard_promise->GetTaskRunner()->PostTask( FROM_HERE, WTF::Bind(&ClipboardPromise::HandleWriteText, WrapPersistent(clipboard_promise), data)); return clipboard_promise->script_promise_resolver_->Promise(); }
void ClipboardPromise::HandleWriteText(const String& data) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); plain_text_ = data; CheckWritePermission(WTF::Bind( &ClipboardPromise::HandleWriteTextWithPermission, WrapPersistent(this))); }
这里取检查权限:
void ClipboardPromise::CheckWritePermission( PermissionService::HasPermissionCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(script_promise_resolver_); if (!IsFocusedDocument(ExecutionContext::From(script_state_))) { script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>( DOMExceptionCode::kNotAllowedError, "Document is not focused.")); return; } if (!GetPermissionService()) { script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>( DOMExceptionCode::kNotAllowedError, "Permission Service could not connect.")); return; }
void ClipboardPromise::HandleWriteTextWithPermission(PermissionStatus status) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (status != PermissionStatus::GRANTED) { script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>( DOMExceptionCode::kNotAllowedError, "Write permission denied.")); return; } 这里将同步调用systemClipboard方法: SystemClipboard::GetInstance().WritePlainText(plain_text_); SystemClipboard::GetInstance().CommitWrite();
这里回调js里promise的resolve方法。(失败的话会调 reject方法)。 script_promise_resolver_->Resolve(); }
4,刚才的同步调用对到这里:
third_party\blink\renderer\core\clipboard\system_clipboard.cc
void SystemClipboard::WritePlainText(const String& plain_text, SmartReplaceOption) { // TODO(https://crbug.com/106449): add support for smart replace, which is // currently under-specified. String text = plain_text; #if defined(OS_WIN) ReplaceNewlinesWithWindowsStyleNewlines(text); #endif clipboard_->WriteText(NonNullString(text)); }
其中
clipboard_是个mojo远程调用对象:mojo::Remote<mojom::blink::ClipboardHost> clipboard_;,实现对象接口在
ClipboardHost中。 5,这里已经跨进程,运行在browser进程中。Host即表示在browser进程中。 content\browser\renderer_host\clipboard_host_impl.cc
void ClipboardHostImpl::WriteText(const base::string16& text) { clipboard_writer_->WriteText(text); }
std::unique_ptr<ui::ScopedClipboardWriter> clipboard_writer_;
6,进入ui\base\clipboard\scoped_clipboard_writer.cc
写其实是压栈:
void ScopedClipboardWriter::WriteText(const base::string16& text) { std::string utf8_text = base::UTF16ToUTF8(text); Clipboard::ObjectMapParams parameters; parameters.push_back( Clipboard::ObjectMapParam(utf8_text.begin(), utf8_text.end())); objects_[Clipboard::ObjectType::kText] = parameters; }
然后调commit:
void ClipboardHostImpl::CommitWrite() { clipboard_writer_.reset( new ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)); }
ScopedClipboardWriter::~ScopedClipboardWriter() { if (!objects_.empty()) Clipboard::GetForCurrentThread()->WriteObjects(buffer_, objects_); }
往下进入win移植层:clipboard_win.cc
void ClipboardWin::WriteObjects(ClipboardBuffer buffer, const ObjectMap& objects) { DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste); ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; ::EmptyClipboard(); for (const auto& object : objects) DispatchObject(object.first, object.second); } void ClipboardWin::WriteText(const char* text_data, size_t text_len) { base::string16 text; base::UTF8ToUTF16(text_data, text_len, &text); HGLOBAL glob = CreateGlobalData(text); WriteToClipboard(CF_UNICODETEXT, glob); }
对于js,promise有 reject,resolve回调,对应cpp中为:
promise被拒:
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>( DOMExceptionCode::kNotAllowedError, "Document is not focused."));
在clipboard写数据后,添加回调通知:
{ ExecutionContext *context= ExecutionContext::From(script_state_); Document* doc = To<Document>(context); LocalDOMWindow* executing_window = doc->ExecutingWindow(); /* call to js */ Event* ce = Event::CreateBubble(event_type_names::kCopy); //(event_interface_names::kCustomEvent); // ce->SetType("copy"); executing_window->DispatchEvent(*ce, NULL); }
js代码:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>我的主页 -</title> <script type="application/javascript" src="jquery.js"></script> <style> .designBtn{ margin-bottom: 20px; } .button{ background-color: #00a4ff; color: #fff; display: inline-block; border-color: #008aff; padding: 6px 3px; } .button:hover{ background-color: #0074ff; } </style> </head> <body> <div class="designBtn"> <a href="${response.encodeURL(ctx+'/user/logout')}" >设计</a> <a href="http://www.163.com" class="sheji" onclick="design(event)">设计</a> </div> <input type="text" name="info" id="info"> <div class="button" >Copy</div> <div class="view"> </div> <script> function getclipcontent(){ navigator.clipboard.readText().then( clipText => { console.log(clipText); showMsg("Success to write clipboard:"+clipText) } ); } navigator.clipboard.addEventListener('clipboardchange', function (event) { console.log("clipboardchange un impl"); navigator.clipboard.readText().then( clipText => { console.log(clipText); } );; }); navigator.clipboard.addEventListener('copy', function (event) { console.log("clipboard listener copy"); //self.setTimeout("getclipcontent()",1);//delay getclipcontent(); ; }); addEventListener('copy', (e) => { console.log("window listener copy"); //self.setTimeout("getclipcontent()",1);//delay getclipcontent(); }); document.addEventListener('copy', (e) => { console.log("document copy listener"); e.preventDefault(); e.clipboardData.setData('text/plain', 'Hello World'); }); var clip={ action:0,//0write,1 paste content:null,//message } function handlePermission() { navigator.permissions.query({name:'clipboard-write'}).then(replyPermission); navigator.permissions.query({name:'clipboard-read'}).then(replyPermission); } function replyPermission(result){ showMsg("permission:"+result.state); if (result.state == 'granted') { // report(result.state); // geoBtn.style.display = 'none'; } else if (result.state == 'prompt') { // report(result.state); // navigator.geolocation.getCurrentPosition(revealPosition,positionDenied,geoSettings); } else if (result.state == 'denied') { // report(result.state); // geoBtn.style.display = 'inline'; showMsg("You've prevented from copy or paste!") } result.onchange = function() { showMsg("permission:"+result.state); } } $(".button").bind('click',function(e){ var info=$("#info").val(); navigator.clipboard.writeText(info).then(function() { navigator.clipboard.readText().then( clipText => { /* clipboard successfully set */ showMsg("Success to write clipboard:"+clipText) } ); }, function() { showMsg("Fail to write clipboard!") /* clipboard write failed */ }); $(".view").append("click!") }) function showMsg(msg){ $(".view").append(msg+"<br>"); } handlePermission(); </script> </body> </html>
代码2:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag --> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> </head> <body > <input type="text" id="contents" /> <input type="button" onClick="copy0()" value="复制" /> <input type="button" onClick="setClipboard()" value="复制剪切板" /> <script type="text/javascript"> function copy0(){ var e=document.getElementById("contents");//对象是contents e.select(); //选择对象 nice=document.execCommand("Copy"); //执行浏览器复制命令 if(nice){ alert('复制内容成功'); } } function setClipboard() { let data = new DataTransfer(); data.items.add("text/plain", "abcd"); navigator.clipboard.write(data).then(function() { /* success */ console.log("copy success.") }, function() { /* failure */ console.log("copy failure.") }); } function oncopy1(){ navigator.clipboard.readText().then( clipText => { //document.querySelector(".cliptext0").innerText = "aaaaaaaaaaaa"; console.log(clipText); } );; } addEventListener('copy', function (event) { navigator.clipboard.readText().then( clipText => { console.log(clipText); } );; }); </script> </body> </html>
js:
const copy = document.getElementById('copyme'); copy.addEventListener('click', copyToClipboard); function copyToClipboard() { // set new clipboard data function setData(e) { e.preventDefault(); e.clipboardData.setData('text/plain', 'Hello, world!'); document.removeEventListener('copy', setData); }; document.addEventListener('copy', setData); let result; try { // execute copy command document.execCommand('copy'); result = 'Copied'; } catch (err) { console.log(err); result = 'Unable to copy'; } // show whether copy was successfull document.getElementById('result').innerHTML = result; setTimeout(() => document.getElementById('result').innerHTML = '', 3000); };
<a href="https://w3c.github.io/clipboard-apis/#clipboard-event-api"> <h3>Clipboard Event API (synchronous)</h3> </a> <button id="copyme"> Copy Me! </button> <br/> <div id="result"></div> <br/> <hr/><br/> Try to paste 😉 <br/> <textarea rows="4" cols="40"></textarea>
ctl+v触发粘贴事件:
js设置:
document.addEventListener('paste', this.pasteHandler, false) 对于粘贴内容获取: pasteHandler(event) { // 阻止默认的粘贴行为 event.preventDefault(); var paste = "" var type = 0; var isFile = false; // 获取剪贴板数据 var clipboardData = event.clipboardData || window.clipboardData; var types = clipboardData.types; if (types) { console.log("all types:", types) if (types.indexOf('text/html') > -1) { // HTML content var htmlContent = clipboardData.getData('text/html'); console.log('HTML content:', htmlContent); if (htmlContent) { paste = htmlContent type = 1; } } else { // Image content const files = clipboardData.files; console.log('Image content:', files); if (files.length > 0) { isFile = true; for (let i = 0; i < files.length; i++) { var reader = new FileReader(); reader.onload = (event) => { const imageUrl = event.target.result; console.log(imageUrl); // This is the image as a data URL this.ws.send(keyboard.pasteText(this.lastId, 3, imageUrl)) }; reader.readAsDataURL(files[i]); } } else { // 如果没有HTML,尝试获取文本 var plainTextContent = clipboardData.getData('text/plain'); console.log('Plain text content pasted:', plainTextContent); paste = plainTextContent type = 0; } } if (!isFile && paste.length > 0) this.ws.send(keyboard.pasteText(this.lastId, type, paste)) else { if (!isFile && paste.length < 1) console.log("=== No content to paste! ===") } }//types },
触发的事件event回传给客户端js定义在
D:\chromium110\chromium\src\third_party\blink\renderer\core\events\clipboard_event.idl
[ Exposed=Window ] interface ClipboardEvent : Event { constructor(DOMString type, optional ClipboardEventInit eventInitDict = {}); readonly attribute DataTransfer? clipboardData; };
可以看到clipboardData是个 DataTransfer 类型对象,有个 files
属性。对应的上面js代码的访问。
[ Exposed=Window ] interface DataTransfer { constructor(); attribute DOMString dropEffect; attribute DOMString effectAllowed; [SameObject] readonly attribute DataTransferItemList items; void setDragImage(Element image, long x, long y); /* old interface */ [CachedAttribute=hasDataStoreItemListChanged] readonly attribute FrozenArray<DOMString> types; DOMString getData(DOMString format); void setData(DOMString format, DOMString data); void clearData(optional DOMString format); [SameObject] readonly attribute FileList files; };
ClipboardEvent生成对应的cpp代码:
D:\chromium110\chromium\src\third_party\blink\renderer\core\events\clipboard_event.h class ClipboardEventInit; class ClipboardEvent final : public Event { DEFINE_WRAPPERTYPEINFO(); public: ClipboardEvent(const AtomicString& type, DataTransfer* clipboard_data); ClipboardEvent(const AtomicString& type, const ClipboardEventInit*); ~ClipboardEvent() override; static ClipboardEvent* Create(const AtomicString& type, DataTransfer* data_transfer) { return MakeGarbageCollected<ClipboardEvent>(type, data_transfer); } static ClipboardEvent* Create(const AtomicString& type, const ClipboardEventInit* initializer) { return MakeGarbageCollected<ClipboardEvent>(type, initializer); } DataTransfer* clipboardData() const { return clipboard_data_.Get(); } void Trace(Visitor*) const override; private: const AtomicString& InterfaceName() const override; bool IsClipboardEvent() const override; Member<DataTransfer> clipboard_data_; };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-07-16 使用 MongoDB 存储日志数据
2018-07-16 MongoDB存储引擎选择
2015-07-16 EJB3 QL查询
2013-07-16 libcurl使用easy模式阻塞卡死等问题的完美解决