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