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_;
};