1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "js_native_api_v8_inspector.h"
17
18 #include <unistd.h>
19
20 #include "inspector_socket_server.h"
21 #include "inspector_utils.h"
22 #include "js_native_api_v8.h"
23 #include "jsvm_mutex.h"
24 #include "libplatform/libplatform.h"
25 #include "v8-inspector.h"
26 #include "v8-platform.h"
27
28 #ifdef __POSIX__
29 #include <climits> // PTHREAD_STACK_MIN
30 #include <pthread.h>
31 #endif // __POSIX__
32
33 #include <algorithm>
34 #include <cstring>
35 #include <securec.h>
36 #include <sstream>
37 #include <unordered_map>
38 #include <vector>
39
40 namespace v8impl {
41
42 namespace {
43 using jsvm::ConditionVariable;
44 using jsvm::Mutex;
45 using jsvm::inspector::StringViewToUtf8;
46 using jsvm::inspector::Utf8ToStringView;
47 using v8_inspector::StringBuffer;
48 using v8_inspector::StringView;
49
50 class MainThreadInterface;
51
52 class Request {
53 public:
54 virtual void Call(MainThreadInterface*) = 0;
55 virtual ~Request() = default;
56 };
57
58 class Deletable {
59 public:
60 virtual ~Deletable() = default;
61 };
62
63 using MessageQueue = std::deque<std::unique_ptr<Request>>;
64
65 class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> {
66 public:
MainThreadHandle(MainThreadInterface * mainThread)67 explicit MainThreadHandle(MainThreadInterface* mainThread) : mainThread(mainThread) {}
~MainThreadHandle()68 ~MainThreadHandle()
69 {
70 Mutex::ScopedLock scopedLock(blockLock);
71 CHECK_NULL(mainThread); // mainThread should have called Reset
72 }
73 std::unique_ptr<InspectorSession> Connect(std::unique_ptr<InspectorSessionDelegate> delegate, bool preventShutdown);
NewObjectId()74 int NewObjectId()
75 {
76 return ++nextObjectId;
77 }
78 bool Post(std::unique_ptr<Request> request);
79
80 private:
81 void Reset();
82
83 MainThreadInterface* mainThread;
84 Mutex blockLock;
85 int nextSessionId = 0;
86 std::atomic_int nextObjectId = { 1 };
87
88 friend class MainThreadInterface;
89 };
90
91 class MainThreadInterface : public std::enable_shared_from_this<MainThreadInterface> {
92 public:
93 explicit MainThreadInterface(Agent* agent);
94 ~MainThreadInterface();
95
96 void DispatchMessages();
97 void Post(std::unique_ptr<Request> request);
98 bool WaitForFrontendEvent();
99 std::shared_ptr<MainThreadHandle> GetHandle();
InspectorAgent()100 Agent* InspectorAgent()
101 {
102 return agent;
103 }
104 void AddObject(int handle, std::unique_ptr<Deletable> object);
105 Deletable* GetObject(int id);
106 Deletable* GetObjectIfExists(int id);
107 void RemoveObject(int handle);
108
109 private:
110 MessageQueue requests;
111 Mutex requestsLock; // requests live across threads
112 // This queue is to maintain the order of the messages for the cases
113 // when we reenter the DispatchMessages function.
114 MessageQueue dispatchingMessageQueue;
115 bool dispatchingMessages = false;
116 ConditionVariable incomingMessageCond;
117 // Used from any thread
118 Agent* const agent;
119 std::shared_ptr<MainThreadHandle> handle;
120 std::unordered_map<int, std::unique_ptr<Deletable>> managedObjects;
121 };
122
123 template<typename T>
124 class DeletableWrapper : public Deletable {
125 public:
DeletableWrapper(std::unique_ptr<T> object)126 explicit DeletableWrapper(std::unique_ptr<T> object) : object(std::move(object)) {}
127 ~DeletableWrapper() override = default;
128
Get(MainThreadInterface * thread,int id)129 static T* Get(MainThreadInterface* thread, int id)
130 {
131 return static_cast<DeletableWrapper<T>*>(thread->GetObject(id))->object.get();
132 }
133
134 private:
135 std::unique_ptr<T> object;
136 };
137
138 template<typename T>
WrapInDeletable(std::unique_ptr<T> object)139 std::unique_ptr<Deletable> WrapInDeletable(std::unique_ptr<T> object)
140 {
141 return std::make_unique<DeletableWrapper<T>>(std::move(object));
142 }
143
144 template<typename Factory>
145 class CreateObjectRequest : public Request {
146 public:
CreateObjectRequest(int objectId,Factory factory)147 CreateObjectRequest(int objectId, Factory factory) : objectId(objectId), factory(std::move(factory)) {}
148
Call(MainThreadInterface * thread)149 void Call(MainThreadInterface* thread) override
150 {
151 thread->AddObject(objectId, WrapInDeletable(factory(thread)));
152 }
153
154 private:
155 int objectId;
156 Factory factory;
157 };
158
159 template<typename Factory>
NewCreateRequest(int objectId,Factory factory)160 std::unique_ptr<Request> NewCreateRequest(int objectId, Factory factory)
161 {
162 return std::make_unique<CreateObjectRequest<Factory>>(objectId, std::move(factory));
163 }
164
165 class DeleteRequest : public Request {
166 public:
DeleteRequest(int objectId)167 explicit DeleteRequest(int objectId) : objectId(objectId) {}
168
Call(MainThreadInterface * thread)169 void Call(MainThreadInterface* thread) override
170 {
171 thread->RemoveObject(objectId);
172 }
173
174 private:
175 int objectId;
176 };
177
178 template<typename Target, typename Fn>
179 class CallRequest : public Request {
180 public:
CallRequest(int id,Fn fn)181 CallRequest(int id, Fn fn) : id(id), fn(std::move(fn)) {}
182
Call(MainThreadInterface * thread)183 void Call(MainThreadInterface* thread) override
184 {
185 fn(DeletableWrapper<Target>::Get(thread, id));
186 }
187
188 private:
189 int id;
190 Fn fn;
191 };
192
193 template<typename T>
194 class AnotherThreadObjectReference {
195 public:
AnotherThreadObjectReference(std::shared_ptr<MainThreadHandle> thread,int objectId)196 AnotherThreadObjectReference(std::shared_ptr<MainThreadHandle> thread, int objectId)
197 : thread(thread), objectId(objectId)
198 {}
199
200 template<typename Factory>
AnotherThreadObjectReference(std::shared_ptr<MainThreadHandle> thread,Factory factory)201 AnotherThreadObjectReference(std::shared_ptr<MainThreadHandle> thread, Factory factory)
202 : AnotherThreadObjectReference(thread, thread->NewObjectId())
203 {
204 thread->Post(NewCreateRequest(objectId, std::move(factory)));
205 }
206
207 AnotherThreadObjectReference(AnotherThreadObjectReference&) = delete;
208
209 AnotherThreadObjectReference& operator=(const AnotherThreadObjectReference&) = delete;
210
~AnotherThreadObjectReference()211 ~AnotherThreadObjectReference()
212 {
213 // Disappearing thread may cause a memory leak
214 thread->Post(std::make_unique<DeleteRequest>(objectId));
215 }
216
217 template<typename Fn>
Call(Fn fn) const218 void Call(Fn fn) const
219 {
220 using Request = CallRequest<T, Fn>;
221 thread->Post(std::make_unique<Request>(objectId, std::move(fn)));
222 }
223
224 template<typename Arg>
Call(void (T::* fn)(Arg),Arg argument) const225 void Call(void (T::*fn)(Arg), Arg argument) const
226 {
227 Call(std::bind(Apply<Arg>, std::placeholders::_1, fn, std::move(argument)));
228 }
229
230 private:
231 // This has to use non-const reference to support std::bind with non-copyable
232 // types
233 template<typename Argument>
Apply(T * target,void (T::* fn)(Argument),Argument & argument)234 static void Apply(T* target, void (T::*fn)(Argument), Argument& argument) /* NOLINT (runtime/references) */
235 {
236 (target->*fn)(std::move(argument));
237 }
238
239 std::shared_ptr<MainThreadHandle> thread;
240 const int objectId;
241 };
242
243 class MainThreadSessionState {
244 public:
MainThreadSessionState(MainThreadInterface * thread,bool preventShutdown)245 MainThreadSessionState(MainThreadInterface* thread, bool preventShutdown)
246 : thread(thread), preventShutdown(preventShutdown)
247 {}
248
Create(MainThreadInterface * thread,bool preventShutdown)249 static std::unique_ptr<MainThreadSessionState> Create(MainThreadInterface* thread, bool preventShutdown)
250 {
251 return std::make_unique<MainThreadSessionState>(thread, preventShutdown);
252 }
253
Connect(std::unique_ptr<InspectorSessionDelegate> delegate)254 void Connect(std::unique_ptr<InspectorSessionDelegate> delegate)
255 {
256 Agent* agent = thread->InspectorAgent();
257 if (agent != nullptr) {
258 session = agent->Connect(std::move(delegate), preventShutdown);
259 }
260 }
261
Dispatch(std::unique_ptr<StringBuffer> message)262 void Dispatch(std::unique_ptr<StringBuffer> message)
263 {
264 session->Dispatch(message->string());
265 }
266
267 private:
268 MainThreadInterface* thread;
269 bool preventShutdown;
270 std::unique_ptr<InspectorSession> session;
271 };
272
273 class CrossThreadInspectorSession : public InspectorSession {
274 public:
CrossThreadInspectorSession(int id,std::shared_ptr<MainThreadHandle> thread,std::unique_ptr<InspectorSessionDelegate> delegate,bool preventShutdown)275 CrossThreadInspectorSession(int id,
276 std::shared_ptr<MainThreadHandle> thread,
277 std::unique_ptr<InspectorSessionDelegate> delegate,
278 bool preventShutdown)
279 : state(thread, std::bind(MainThreadSessionState::Create, std::placeholders::_1, preventShutdown))
280 {
281 state.Call(&MainThreadSessionState::Connect, std::move(delegate));
282 }
283
Dispatch(const StringView & message)284 void Dispatch(const StringView& message) override
285 {
286 state.Call(&MainThreadSessionState::Dispatch, StringBuffer::create(message));
287 }
288
289 private:
290 AnotherThreadObjectReference<MainThreadSessionState> state;
291 };
292
293 class ThreadSafeDelegate : public InspectorSessionDelegate {
294 public:
ThreadSafeDelegate(std::shared_ptr<MainThreadHandle> thread,int objectId)295 ThreadSafeDelegate(std::shared_ptr<MainThreadHandle> thread, int objectId)
296 : thread(thread), delegate(thread, objectId)
297 {}
298
SendMessageToFrontend(const v8_inspector::StringView & message)299 void SendMessageToFrontend(const v8_inspector::StringView& message) override
300 {
301 delegate.Call([m = StringBuffer::create(message)](InspectorSessionDelegate* delegate) {
302 delegate->SendMessageToFrontend(m->string());
303 });
304 }
305
306 private:
307 std::shared_ptr<MainThreadHandle> thread;
308 AnotherThreadObjectReference<InspectorSessionDelegate> delegate;
309 };
310
MainThreadInterface(Agent * agent)311 MainThreadInterface::MainThreadInterface(Agent* agent) : agent(agent) {}
312
~MainThreadInterface()313 MainThreadInterface::~MainThreadInterface()
314 {
315 if (handle) {
316 handle->Reset();
317 }
318 }
319
Post(std::unique_ptr<Request> request)320 void MainThreadInterface::Post(std::unique_ptr<Request> request)
321 {
322 CHECK_NOT_NULL(agent);
323 Mutex::ScopedLock scopedLock(requestsLock);
324 bool needsNotify = requests.empty();
325 requests.push_back(std::move(request));
326 if (needsNotify) {
327 std::weak_ptr<MainThreadInterface> weakSelf { shared_from_this() };
328 agent->env()->RequestInterrupt([weakSelf](Environment*) {
329 if (auto iface = weakSelf.lock()) {
330 iface->DispatchMessages();
331 }
332 });
333 }
334 incomingMessageCond.Broadcast(scopedLock);
335 }
336
WaitForFrontendEvent()337 bool MainThreadInterface::WaitForFrontendEvent()
338 {
339 // We allow DispatchMessages reentry as we enter the pause. This is important
340 // to support debugging the code invoked by an inspector call, such
341 // as Runtime.evaluate
342 dispatchingMessages = false;
343 if (dispatchingMessageQueue.empty()) {
344 Mutex::ScopedLock scopedLock(requestsLock);
345 while (requests.empty())
346 incomingMessageCond.Wait(scopedLock);
347 }
348 return true;
349 }
350
DispatchMessages()351 void MainThreadInterface::DispatchMessages()
352 {
353 if (dispatchingMessages) {
354 return;
355 }
356 dispatchingMessages = true;
357 bool hadMessages = false;
358 do {
359 if (dispatchingMessageQueue.empty()) {
360 Mutex::ScopedLock scopedLock(requestsLock);
361 requests.swap(dispatchingMessageQueue);
362 }
363 hadMessages = !dispatchingMessageQueue.empty();
364 while (!dispatchingMessageQueue.empty()) {
365 MessageQueue::value_type task;
366 std::swap(dispatchingMessageQueue.front(), task);
367 dispatchingMessageQueue.pop_front();
368
369 v8::SealHandleScope sealHandleScope(agent->env()->isolate);
370 task->Call(this);
371 }
372 } while (hadMessages);
373 dispatchingMessages = false;
374 }
375
GetHandle()376 std::shared_ptr<MainThreadHandle> MainThreadInterface::GetHandle()
377 {
378 if (handle == nullptr) {
379 handle = std::make_shared<MainThreadHandle>(this);
380 }
381 return handle;
382 }
383
AddObject(int id,std::unique_ptr<Deletable> object)384 void MainThreadInterface::AddObject(int id, std::unique_ptr<Deletable> object)
385 {
386 CHECK_NOT_NULL(object);
387 managedObjects[id] = std::move(object);
388 }
389
RemoveObject(int handle)390 void MainThreadInterface::RemoveObject(int handle)
391 {
392 CHECK_EQ(1, managedObjects.erase(handle));
393 }
394
GetObject(int id)395 Deletable* MainThreadInterface::GetObject(int id)
396 {
397 Deletable* pointer = GetObjectIfExists(id);
398 // This would mean the object is requested after it was disposed, which is
399 // a coding error.
400 CHECK_NOT_NULL(pointer);
401 return pointer;
402 }
403
GetObjectIfExists(int id)404 Deletable* MainThreadInterface::GetObjectIfExists(int id)
405 {
406 auto iterator = managedObjects.find(id);
407 if (iterator == managedObjects.end()) {
408 return nullptr;
409 }
410 return iterator->second.get();
411 }
412
Connect(std::unique_ptr<InspectorSessionDelegate> delegate,bool preventShutdown)413 std::unique_ptr<InspectorSession> MainThreadHandle::Connect(std::unique_ptr<InspectorSessionDelegate> delegate,
414 bool preventShutdown)
415 {
416 return std::unique_ptr<InspectorSession>(
417 new CrossThreadInspectorSession(++nextSessionId, shared_from_this(), std::move(delegate), preventShutdown));
418 }
419
Post(std::unique_ptr<Request> request)420 bool MainThreadHandle::Post(std::unique_ptr<Request> request)
421 {
422 Mutex::ScopedLock scopedLock(blockLock);
423 if (!mainThread) {
424 return false;
425 }
426 mainThread->Post(std::move(request));
427 return true;
428 }
429
Reset()430 void MainThreadHandle::Reset()
431 {
432 Mutex::ScopedLock scopedLock(blockLock);
433 mainThread = nullptr;
434 }
435 } // namespace
436
437 namespace {
438 using jsvm::InspectPublishUid;
439 using jsvm::inspector::CheckedUvLoopClose;
440 using jsvm::inspector::CSPRNG;
441 using jsvm::inspector::FormatWsAddress;
442 using jsvm::inspector::GetHumanReadableProcessName;
443 using jsvm::inspector::InspectorSocketServer;
444
445 // K_KILL closes connections and stops the server, K_STOP only stops the server
446 enum class TransportAction { K_KILL, K_SEND_MESSAGE, K_STOP };
447
ScriptPath(uv_loop_t * loop,const std::string & scriptName)448 std::string ScriptPath(uv_loop_t* loop, const std::string& scriptName)
449 {
450 std::string scriptPath;
451
452 if (!scriptName.empty()) {
453 uv_fs_t req;
454 req.ptr = nullptr;
455 if (0 == uv_fs_realpath(loop, &req, scriptName.c_str(), nullptr)) {
456 CHECK_NOT_NULL(req.ptr);
457 scriptPath = std::string(static_cast<char*>(req.ptr));
458 }
459 uv_fs_req_cleanup(&req);
460 }
461
462 return scriptPath;
463 }
464
465 // UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
466 // Used ver 4 - with numbers
GenerateID()467 std::string GenerateID()
468 {
469 uint16_t buffer[8];
470 CHECK(CSPRNG(buffer, sizeof(buffer)));
471
472 char uuid[256];
473 int ret = snprintf_s(uuid, sizeof(uuid), sizeof(uuid) - 1, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
474 buffer[0], // time_low
475 buffer[1], // time_mid
476 buffer[2], // time_low
477 (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
478 (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
479 buffer[5], // node
480 buffer[6], buffer[7]);
481 CHECK(ret >= 0);
482 return uuid;
483 }
484
485 class RequestToServer {
486 public:
RequestToServer(TransportAction action,int sessionId,std::unique_ptr<v8_inspector::StringBuffer> message)487 RequestToServer(TransportAction action, int sessionId, std::unique_ptr<v8_inspector::StringBuffer> message)
488 : action(action), sessionId(sessionId), message(std::move(message))
489 {}
490
Dispatch(InspectorSocketServer * server) const491 void Dispatch(InspectorSocketServer* server) const
492 {
493 switch (action) {
494 case TransportAction::K_KILL:
495 server->TerminateConnections();
496 [[fallthrough]];
497 case TransportAction::K_STOP:
498 server->Stop();
499 break;
500 case TransportAction::K_SEND_MESSAGE:
501 server->Send(sessionId, StringViewToUtf8(message->string()));
502 break;
503 }
504 }
505
506 private:
507 TransportAction action;
508 int sessionId;
509 std::unique_ptr<v8_inspector::StringBuffer> message;
510 };
511
512 class RequestQueue;
513
514 class RequestQueueData {
515 public:
516 using MessageQueue = std::deque<RequestToServer>;
517
RequestQueueData(uv_loop_t * loop)518 explicit RequestQueueData(uv_loop_t* loop) : handle(std::make_shared<RequestQueue>(this))
519 {
520 int err = uv_async_init(loop, &async, [](uv_async_t* async) {
521 RequestQueueData* wrapper = jsvm::inspector::ContainerOf(&RequestQueueData::async, async);
522 wrapper->DoDispatch();
523 });
524 CHECK_EQ(0, err);
525 }
526
527 static void CloseAndFree(RequestQueueData* queue);
528
Post(int sessionId,TransportAction action,std::unique_ptr<StringBuffer> message)529 void Post(int sessionId, TransportAction action, std::unique_ptr<StringBuffer> message)
530 {
531 Mutex::ScopedLock scopedLock(stateLock);
532 bool notify = messages.empty();
533 messages.emplace_back(action, sessionId, std::move(message));
534 if (notify) {
535 CHECK_EQ(0, uv_async_send(&async));
536 incomingMessageCond.Broadcast(scopedLock);
537 }
538 }
539
Wait()540 void Wait()
541 {
542 Mutex::ScopedLock scopedLock(stateLock);
543 if (messages.empty()) {
544 incomingMessageCond.Wait(scopedLock);
545 }
546 }
547
SetServer(InspectorSocketServer * serverParam)548 void SetServer(InspectorSocketServer* serverParam)
549 {
550 server = serverParam;
551 }
552
GetHandle()553 std::shared_ptr<RequestQueue> GetHandle()
554 {
555 return handle;
556 }
557
558 private:
559 ~RequestQueueData() = default;
560
GetMessages()561 MessageQueue GetMessages()
562 {
563 Mutex::ScopedLock scopedLock(stateLock);
564 MessageQueue messagesQ;
565 messages.swap(messagesQ);
566 return messagesQ;
567 }
568
DoDispatch()569 void DoDispatch()
570 {
571 if (server == nullptr) {
572 return;
573 }
574 for (const auto& request : GetMessages()) {
575 request.Dispatch(server);
576 }
577 }
578
579 std::shared_ptr<RequestQueue> handle;
580 uv_async_t async;
581 InspectorSocketServer* server = nullptr;
582 MessageQueue messages;
583 Mutex stateLock; // Locked before mutating the queue.
584 ConditionVariable incomingMessageCond;
585 };
586
587 class RequestQueue {
588 public:
RequestQueue(RequestQueueData * data)589 explicit RequestQueue(RequestQueueData* data) : data(data) {}
590
Reset()591 void Reset()
592 {
593 Mutex::ScopedLock scopedLock(lock);
594 data = nullptr;
595 }
596
Post(int sessionId,TransportAction action,std::unique_ptr<StringBuffer> message)597 void Post(int sessionId, TransportAction action, std::unique_ptr<StringBuffer> message)
598 {
599 Mutex::ScopedLock scopedLock(lock);
600 if (data != nullptr) {
601 data->Post(sessionId, action, std::move(message));
602 }
603 }
604
Expired()605 bool Expired()
606 {
607 Mutex::ScopedLock scopedLock(lock);
608 return data == nullptr;
609 }
610
611 private:
612 RequestQueueData* data;
613 Mutex lock;
614 };
615
616 class IoSessionDelegate : public InspectorSessionDelegate {
617 public:
IoSessionDelegate(std::shared_ptr<RequestQueue> queue,int id)618 explicit IoSessionDelegate(std::shared_ptr<RequestQueue> queue, int id) : requestQueue(queue), id(id) {}
SendMessageToFrontend(const v8_inspector::StringView & message)619 void SendMessageToFrontend(const v8_inspector::StringView& message) override
620 {
621 requestQueue->Post(id, TransportAction::K_SEND_MESSAGE, StringBuffer::create(message));
622 }
623
624 private:
625 std::shared_ptr<RequestQueue> requestQueue;
626 int id;
627 };
628
629 // Passed to InspectorSocketServer to handle WS inspector protocol events,
630 // mostly session start, message received, and session end.
631 class InspectorIoDelegate : public jsvm::inspector::SocketServerDelegate {
632 public:
633 InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue,
634 std::shared_ptr<MainThreadHandle> mainThread,
635 const std::string& targetId,
636 const std::string& scriptPath,
637 const std::string& scriptName);
638 ~InspectorIoDelegate() override = default;
639
640 void StartSession(int sessionId, const std::string& inTargetId) override;
641 void MessageReceived(int sessionId, const std::string& message) override;
642 void EndSession(int sessionId) override;
643
644 std::vector<std::string> GetTargetIds() override;
645 std::string GetTargetTitle(const std::string& id) override;
646 std::string GetTargetUrl(const std::string& id) override;
AssignServer(InspectorSocketServer * server)647 void AssignServer(InspectorSocketServer* server) override
648 {
649 requestQueue->SetServer(server);
650 }
651
652 private:
653 std::shared_ptr<RequestQueueData> requestQueue;
654 std::shared_ptr<MainThreadHandle> mainThread;
655 std::unordered_map<int, std::unique_ptr<InspectorSession>> sessions;
656 const std::string scriptName;
657 const std::string scriptPath;
658 const std::string targetId;
659 };
660
InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue,std::shared_ptr<MainThreadHandle> mainThread,const std::string & targetId,const std::string & scriptPath,const std::string & scriptName)661 InspectorIoDelegate::InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue,
662 std::shared_ptr<MainThreadHandle> mainThread,
663 const std::string& targetId,
664 const std::string& scriptPath,
665 const std::string& scriptName)
666 : requestQueue(queue), mainThread(mainThread), scriptName(scriptName), scriptPath(scriptPath), targetId(targetId)
667 {}
668
StartSession(int sessionId,const std::string & inTargetId)669 void InspectorIoDelegate::StartSession(int sessionId, const std::string& inTargetId)
670 {
671 auto session = mainThread->Connect(std::make_unique<IoSessionDelegate>(requestQueue->GetHandle(), sessionId), true);
672 if (session) {
673 sessions[sessionId] = std::move(session);
674 if (fprintf(stderr, "Debugger attached.\n") < 0) {
675 return;
676 }
677 }
678 }
679
MessageReceived(int sessionId,const std::string & message)680 void InspectorIoDelegate::MessageReceived(int sessionId, const std::string& message)
681 {
682 auto session = sessions.find(sessionId);
683 if (session != sessions.end()) {
684 session->second->Dispatch(Utf8ToStringView(message)->string());
685 }
686 }
687
EndSession(int sessionId)688 void InspectorIoDelegate::EndSession(int sessionId)
689 {
690 sessions.erase(sessionId);
691 }
692
GetTargetIds()693 std::vector<std::string> InspectorIoDelegate::GetTargetIds()
694 {
695 return { targetId };
696 }
697
GetTargetTitle(const std::string & id)698 std::string InspectorIoDelegate::GetTargetTitle(const std::string& id)
699 {
700 return scriptName.empty() ? GetHumanReadableProcessName() : scriptName;
701 }
702
GetTargetUrl(const std::string & id)703 std::string InspectorIoDelegate::GetTargetUrl(const std::string& id)
704 {
705 return "file://" + scriptPath;
706 }
707
708 // static
CloseAndFree(RequestQueueData * queue)709 void RequestQueueData::CloseAndFree(RequestQueueData* queue)
710 {
711 queue->handle->Reset();
712 queue->handle.reset();
713 uv_close(reinterpret_cast<uv_handle_t*>(&queue->async), [](uv_handle_t* handle) {
714 uv_async_t* async = reinterpret_cast<uv_async_t*>(handle);
715 RequestQueueData* wrapper = jsvm::inspector::ContainerOf(&RequestQueueData::async, async);
716 delete wrapper;
717 });
718 }
719 } // namespace
720
721 class InspectorIo {
722 public:
723 // Start the inspector agent thread, waiting for it to initialize.
724 // Returns empty pointer if thread was not started.
725 static std::unique_ptr<InspectorIo> Start(std::shared_ptr<MainThreadHandle> mainThread,
726 const std::string& path,
727 std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,
728 const jsvm::InspectPublishUid& inspectPublishUid);
729
730 // Will block till the transport thread shuts down
731 ~InspectorIo();
732
733 void StopAcceptingNewConnections();
734 std::string GetWsUrl() const;
735
736 private:
737 InspectorIo(std::shared_ptr<MainThreadHandle> handle,
738 const std::string& path,
739 std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,
740 const jsvm::InspectPublishUid& inspectPublishUid);
741
742 // Wrapper for agent->ThreadMain()
743 static void ThreadMain(void* io);
744
745 // Runs a uv_loop_t
746 void ThreadMain();
747
748 // This is a thread-safe object that will post async tasks. It lives as long
749 // as an Inspector object lives (almost as long as an Isolate).
750 std::shared_ptr<MainThreadHandle> mainThread;
751 // Used to post on a frontend interface thread, lives while the server is
752 // running
753 std::shared_ptr<RequestQueue> requestQueue;
754 std::shared_ptr<ExclusiveAccess<HostPort>> hostPort;
755 jsvm::InspectPublishUid inspectPublishUid;
756
757 // The IO thread runs its own uv_loop to implement the TCP server off
758 // the main thread.
759 uv_thread_t thread;
760
761 // For setting up interthread communications
762 Mutex threadStartLock;
763 jsvm::ConditionVariable threadStartCondition;
764 std::string scriptName;
765 // May be accessed from any thread
766 const std::string id;
767 };
768
769 // static
Start(std::shared_ptr<MainThreadHandle> mainThread,const std::string & path,std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,const InspectPublishUid & inspectPublishUid)770 std::unique_ptr<InspectorIo> InspectorIo::Start(std::shared_ptr<MainThreadHandle> mainThread,
771 const std::string& path,
772 std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,
773 const InspectPublishUid& inspectPublishUid)
774 {
775 auto io = std::unique_ptr<InspectorIo>(new InspectorIo(mainThread, path, hostPortParam, inspectPublishUid));
776 if (io->requestQueue->Expired()) { // Thread is not running
777 return nullptr;
778 }
779 return io;
780 }
781
InspectorIo(std::shared_ptr<MainThreadHandle> mainThread,const std::string & path,std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,const InspectPublishUid & inspectPublishUid)782 InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> mainThread,
783 const std::string& path,
784 std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,
785 const InspectPublishUid& inspectPublishUid)
786 : mainThread(mainThread), hostPort(hostPortParam), inspectPublishUid(inspectPublishUid), thread(), scriptName(path),
787 id(GenerateID())
788 {
789 Mutex::ScopedLock scopedLock(threadStartLock);
790 CHECK_EQ(uv_thread_create(&thread, InspectorIo::ThreadMain, this), 0);
791 threadStartCondition.Wait(scopedLock);
792 }
793
~InspectorIo()794 InspectorIo::~InspectorIo()
795 {
796 requestQueue->Post(0, TransportAction::K_KILL, nullptr);
797 int err = uv_thread_join(&thread);
798 CHECK_EQ(err, 0);
799 }
800
StopAcceptingNewConnections()801 void InspectorIo::StopAcceptingNewConnections()
802 {
803 requestQueue->Post(0, TransportAction::K_STOP, nullptr);
804 }
805
806 // static
ThreadMain(void * io)807 void InspectorIo::ThreadMain(void* io)
808 {
809 static_cast<InspectorIo*>(io)->ThreadMain();
810 }
811
ThreadMain()812 void InspectorIo::ThreadMain()
813 {
814 uv_loop_t loop;
815 loop.data = nullptr;
816 int err = uv_loop_init(&loop);
817 CHECK_EQ(err, 0);
818 std::shared_ptr<RequestQueueData> queue(new RequestQueueData(&loop), RequestQueueData::CloseAndFree);
819 std::string scriptPath = ScriptPath(&loop, scriptName);
820 std::unique_ptr<InspectorIoDelegate> delegate(
821 new InspectorIoDelegate(queue, mainThread, id, scriptPath, scriptName));
822 std::string host;
823 int port;
824 int pid;
825 {
826 ExclusiveAccess<HostPort>::Scoped scopedHostPort(hostPort);
827 host = scopedHostPort->GetHost();
828 port = scopedHostPort->GetPort();
829 pid = scopedHostPort->GetPid();
830 }
831 InspectorSocketServer server(std::move(delegate), &loop, std::move(host), port, inspectPublishUid, stderr, pid);
832 requestQueue = queue->GetHandle();
833 // Its lifetime is now that of the server delegate
834 queue.reset();
835 {
836 Mutex::ScopedLock scopedLock(threadStartLock);
837 if (server.Start()) {
838 ExclusiveAccess<HostPort>::Scoped scopedHostPort(hostPort);
839 scopedHostPort->SetPort(server.GetPort());
840 }
841 threadStartCondition.Broadcast(scopedLock);
842 }
843 uv_run(&loop, UV_RUN_DEFAULT);
844 CheckedUvLoopClose(&loop);
845 }
846
GetWsUrl() const847 std::string InspectorIo::GetWsUrl() const
848 {
849 ExclusiveAccess<HostPort>::Scoped scopedHostPort(hostPort);
850 return FormatWsAddress(scopedHostPort->GetHost(), scopedHostPort->GetPort(), id, true);
851 }
852
853 namespace {
854
855 using jsvm::inspector::TwoByteValue;
856
857 using v8::Local;
858 using v8::Context;
859 using v8::Function;
860 using v8::HandleScope;
861 using v8::Isolate;
862 using v8::Message;
863 using v8::Object;
864 using v8::Value;
865
866 using v8_inspector::StringBuffer;
867 using v8_inspector::StringView;
868 using v8_inspector::V8InspectorClient;
869 using v8_inspector::V8Inspector;
870
ToProtocolString(Isolate * isolate,Local<Value> value)871 std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate, Local<Value> value)
872 {
873 TwoByteValue buffer(isolate, value);
874 return StringBuffer::create(StringView(*buffer, buffer.GetLength()));
875 }
876
877 const int CONTEXT_GROUP_ID = 1;
878
GetWorkerLabel(Environment * env)879 std::string GetWorkerLabel(Environment* env)
880 {
881 std::ostringstream result;
882 result << "Worker["
883 << "env->thread_id()"
884 << "]";
885 return result.str();
886 }
887
888 class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
889 public:
ChannelImpl(const std::unique_ptr<V8Inspector> & inspector,std::unique_ptr<InspectorSessionDelegate> delegate,std::shared_ptr<MainThreadHandle> mainThread,bool preventShutdown)890 explicit ChannelImpl(const std::unique_ptr<V8Inspector>& inspector,
891 std::unique_ptr<InspectorSessionDelegate> delegate,
892 std::shared_ptr<MainThreadHandle> mainThread,
893 bool preventShutdown)
894 : delegate(std::move(delegate)), preventShutdown(preventShutdown)
895 {
896 session =
897 inspector->connect(CONTEXT_GROUP_ID, this, StringView(), V8Inspector::ClientTrustLevel::kFullyTrusted);
898 }
899
900 ~ChannelImpl() = default;
901
DispatchProtocolMessage(const StringView & message)902 void DispatchProtocolMessage(const StringView& message)
903 {
904 session->dispatchProtocolMessage(message);
905 }
906
SchedulePauseOnNextStatement(const std::string & reason)907 void SchedulePauseOnNextStatement(const std::string& reason)
908 {
909 std::unique_ptr<StringBuffer> buffer = Utf8ToStringView(reason);
910 session->schedulePauseOnNextStatement(buffer->string(), buffer->string());
911 }
912
PreventShutdown()913 bool PreventShutdown()
914 {
915 return preventShutdown;
916 }
917
918 private:
sendResponse(int callId,std::unique_ptr<v8_inspector::StringBuffer> message)919 void sendResponse(int callId, std::unique_ptr<v8_inspector::StringBuffer> message) override
920 {
921 SendMessageToFrontend(message->string());
922 }
923
sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message)924 void sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message) override
925 {
926 SendMessageToFrontend(message->string());
927 }
928
flushProtocolNotifications()929 void flushProtocolNotifications() override {}
930
SendMessageToFrontend(const StringView & message)931 void SendMessageToFrontend(const StringView& message)
932 {
933 delegate->SendMessageToFrontend(message);
934 }
935
sendMessageToFrontend(const std::string & message)936 void sendMessageToFrontend(const std::string& message)
937 {
938 SendMessageToFrontend(Utf8ToStringView(message)->string());
939 }
940
941 std::unique_ptr<InspectorSessionDelegate> delegate;
942 std::unique_ptr<v8_inspector::V8InspectorSession> session;
943 bool preventShutdown;
944 };
945
946 class SameThreadInspectorSession : public InspectorSession {
947 public:
SameThreadInspectorSession(int sessionId,std::shared_ptr<InspectorClient> client)948 SameThreadInspectorSession(int sessionId, std::shared_ptr<InspectorClient> client)
949 : sessionId(sessionId), client(client)
950 {}
951 ~SameThreadInspectorSession() override;
952 void Dispatch(const v8_inspector::StringView& message) override;
953
954 private:
955 int sessionId;
956 std::weak_ptr<InspectorClient> client;
957 };
958
959 } // namespace
960
961 class InspectorClient : public V8InspectorClient {
962 public:
InspectorClient(Environment * env,bool isMain)963 explicit InspectorClient(Environment* env, bool isMain) : env(env), isMain(isMain)
964 {
965 client = V8Inspector::create(env->isolate, this);
966 std::string name = isMain ? GetHumanReadableProcessName() : GetWorkerLabel(env);
967 ContextInfo info(name);
968 info.isDefault = true;
969 ContextCreated(env->context(), info);
970 }
971
runMessageLoopOnPause(int contextGroupId)972 void runMessageLoopOnPause(int contextGroupId) override
973 {
974 waitingForResume = true;
975 RunMessageLoop();
976 }
977
WaitForSessionsDisconnect()978 void WaitForSessionsDisconnect()
979 {
980 waitingForSessionsDisconnect = true;
981 RunMessageLoop();
982 }
983
WaitForFrontend()984 void WaitForFrontend()
985 {
986 waitingForFrontend = true;
987 RunMessageLoop();
988 }
989
maxAsyncCallStackDepthChanged(int depth)990 void maxAsyncCallStackDepthChanged(int depth) override
991 {
992 if (waitingForSessionsDisconnect) {
993 // V8 isolate is mostly done and is only letting Inspector protocol
994 // clients gather data.
995 return;
996 }
997 }
998
ContextCreated(Local<Context> context,const ContextInfo & info)999 void ContextCreated(Local<Context> context, const ContextInfo& info)
1000 {
1001 auto nameBuffer = Utf8ToStringView(info.name);
1002 auto originBuffer = Utf8ToStringView(info.origin);
1003 std::unique_ptr<StringBuffer> auxDataBuffer;
1004
1005 v8_inspector::V8ContextInfo v8info(context, CONTEXT_GROUP_ID, nameBuffer->string());
1006 v8info.origin = originBuffer->string();
1007
1008 if (info.isDefault) {
1009 auxDataBuffer = Utf8ToStringView("{\"isDefault\":true}");
1010 } else {
1011 auxDataBuffer = Utf8ToStringView("{\"isDefault\":false}");
1012 }
1013 v8info.auxData = auxDataBuffer->string();
1014
1015 client->contextCreated(v8info);
1016 }
1017
ContextDestroyed(Local<Context> context)1018 void ContextDestroyed(Local<Context> context)
1019 {
1020 client->contextDestroyed(context);
1021 }
1022
quitMessageLoopOnPause()1023 void quitMessageLoopOnPause() override
1024 {
1025 waitingForResume = false;
1026 }
1027
runIfWaitingForDebugger(int contextGroupId)1028 void runIfWaitingForDebugger(int contextGroupId) override
1029 {
1030 waitingForFrontend = false;
1031 }
1032
ConnectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,bool preventShutdown)1033 int ConnectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate, bool preventShutdown)
1034 {
1035 int sessionId = nextSessionId++;
1036 channels[sessionId] =
1037 std::make_unique<ChannelImpl>(client, std::move(delegate), GetThreadHandle(), preventShutdown);
1038 return sessionId;
1039 }
1040
DisconnectFrontend(int sessionId)1041 void DisconnectFrontend(int sessionId)
1042 {
1043 auto it = channels.find(sessionId);
1044 if (it == channels.end()) {
1045 return;
1046 }
1047 channels.erase(it);
1048 if (waitingForSessionsDisconnect && !isMain) {
1049 waitingForSessionsDisconnect = false;
1050 }
1051 }
1052
DispatchMessageFromFrontend(int sessionId,const StringView & message)1053 void DispatchMessageFromFrontend(int sessionId, const StringView& message)
1054 {
1055 channels[sessionId]->DispatchProtocolMessage(message);
1056 }
1057
ensureDefaultContextInGroup(int contextGroupId)1058 Local<Context> ensureDefaultContextInGroup(int contextGroupId) override
1059 {
1060 return env->context();
1061 }
1062
ReportUncaughtException(Local<Value> error,Local<Message> message)1063 void ReportUncaughtException(Local<Value> error, Local<Message> message)
1064 {
1065 Isolate* isolate = env->isolate;
1066 Local<Context> context = env->context();
1067
1068 int scriptId = message->GetScriptOrigin().ScriptId();
1069
1070 Local<v8::StackTrace> stackTrace = message->GetStackTrace();
1071
1072 if (!stackTrace.IsEmpty() && stackTrace->GetFrameCount() > 0 &&
1073 scriptId == stackTrace->GetFrame(isolate, 0)->GetScriptId()) {
1074 scriptId = 0;
1075 }
1076
1077 const uint8_t details[] = "Uncaught";
1078
1079 client->exceptionThrown(context, StringView(details, sizeof(details) - 1), error,
1080 ToProtocolString(isolate, message->Get())->string(),
1081 ToProtocolString(isolate, message->GetScriptResourceName())->string(),
1082 message->GetLineNumber(context).FromMaybe(0),
1083 message->GetStartColumn(context).FromMaybe(0), client->createStackTrace(stackTrace),
1084 scriptId);
1085 }
1086
startRepeatingTimer(double interval,TimerCallback callback,void * data)1087 void startRepeatingTimer(double interval, TimerCallback callback, void* data) override {}
1088
cancelTimer(void * data)1089 void cancelTimer(void* data) override {}
1090
SchedulePauseOnNextStatement(const std::string & reason)1091 void SchedulePauseOnNextStatement(const std::string& reason)
1092 {
1093 for (const auto& idChannel : channels) {
1094 idChannel.second->SchedulePauseOnNextStatement(reason);
1095 }
1096 }
1097
HasConnectedSessions()1098 bool HasConnectedSessions()
1099 {
1100 for (const auto& idChannel : channels) {
1101 // Other sessions are "invisible" more most purposes
1102 if (idChannel.second->PreventShutdown()) {
1103 return true;
1104 }
1105 }
1106 return false;
1107 }
1108
GetThreadHandle()1109 std::shared_ptr<MainThreadHandle> GetThreadHandle()
1110 {
1111 if (!interface) {
1112 interface = std::make_shared<MainThreadInterface>(static_cast<Agent*>(env->GetInspectorAgent()));
1113 }
1114 return interface->GetHandle();
1115 }
1116
IsActive()1117 bool IsActive()
1118 {
1119 return !channels.empty();
1120 }
1121
1122 private:
ShouldRunMessageLoop()1123 bool ShouldRunMessageLoop()
1124 {
1125 if (waitingForFrontend) {
1126 return true;
1127 }
1128 if (waitingForSessionsDisconnect || waitingForResume) {
1129 return HasConnectedSessions();
1130 }
1131 return false;
1132 }
1133
RunMessageLoop()1134 void RunMessageLoop()
1135 {
1136 if (runningNestedLoop) {
1137 return;
1138 }
1139
1140 runningNestedLoop = true;
1141
1142 while (ShouldRunMessageLoop()) {
1143 if (interface) {
1144 interface->WaitForFrontendEvent();
1145 }
1146 env->RunAndClearInterrupts();
1147 }
1148 runningNestedLoop = false;
1149 }
1150
currentTimeMS()1151 double currentTimeMS() override
1152 {
1153 return env->platform()->CurrentClockTimeMillis();
1154 }
1155
1156 Environment* env;
1157 bool isMain;
1158 bool runningNestedLoop = false;
1159 std::unique_ptr<V8Inspector> client;
1160 std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels;
1161 int nextSessionId = 1;
1162 bool waitingForResume = false;
1163 bool waitingForFrontend = false;
1164 bool waitingForSessionsDisconnect = false;
1165 // Allows accessing Inspector from non-main threads
1166 std::shared_ptr<MainThreadInterface> interface;
1167 };
1168
Agent(Environment * env)1169 Agent::Agent(Environment* env) : parentEnv(env) {}
1170
1171 Agent::~Agent() = default;
1172
Start(const std::string & pathParam,std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,bool isMain,bool waitForConnect)1173 bool Agent::Start(const std::string& pathParam,
1174 std::shared_ptr<ExclusiveAccess<HostPort>> hostPortParam,
1175 bool isMain,
1176 bool waitForConnect)
1177 {
1178 path = pathParam;
1179 CHECK_NOT_NULL(hostPortParam);
1180 hostPort = hostPortParam;
1181
1182 client = std::make_shared<InspectorClient>(parentEnv, isMain);
1183
1184 if (!StartIoThread()) {
1185 return false;
1186 }
1187
1188 if (waitForConnect) {
1189 client->WaitForFrontend();
1190 }
1191 return true;
1192 }
1193
FindAvailablePort()1194 int FindAvailablePort()
1195 {
1196 constexpr int startPort = 9229;
1197 constexpr int endPort = 9999;
1198 constexpr int invalidPort = -1;
1199 int sockfd = -1;
1200
1201 for (auto port = startPort; port <= endPort; ++port) {
1202 sockfd = socket(AF_INET, SOCK_STREAM, 0);
1203 if (sockfd < 0) {
1204 continue;
1205 }
1206 struct sockaddr_in addr;
1207 addr.sin_family = AF_INET;
1208 addr.sin_addr.s_addr = htonl(INADDR_ANY);
1209 addr.sin_port = htons(port);
1210
1211 if (bind(sockfd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
1212 close(sockfd);
1213 if (errno == EADDRINUSE) {
1214 continue;
1215 } else {
1216 break;
1217 }
1218 }
1219 close(sockfd);
1220 return port;
1221 }
1222 return invalidPort;
1223 }
1224
Start(const std::string & pathParam,int pid)1225 bool Agent::Start(const std::string& pathParam, int pid)
1226 {
1227 int port = FindAvailablePort();
1228 if (port < 0) {
1229 return false;
1230 }
1231 auto hostPort = std::make_shared<jsvm::ExclusiveAccess<jsvm::HostPort>>("localhost", port, pid);
1232 return Start(pathParam, hostPort, true, false);
1233 }
1234
StartIoThread()1235 bool Agent::StartIoThread()
1236 {
1237 if (io != nullptr) {
1238 return true;
1239 }
1240
1241 if (!client) {
1242 return false;
1243 }
1244
1245 CHECK_NOT_NULL(client);
1246
1247 io = InspectorIo::Start(client->GetThreadHandle(), path, hostPort, { false, true });
1248 if (io == nullptr) {
1249 return false;
1250 }
1251 return true;
1252 }
1253
Stop()1254 void Agent::Stop()
1255 {
1256 io.reset();
1257 }
1258
Connect(std::unique_ptr<InspectorSessionDelegate> delegate,bool preventShutdown)1259 std::unique_ptr<InspectorSession> Agent::Connect(std::unique_ptr<InspectorSessionDelegate> delegate,
1260 bool preventShutdown)
1261 {
1262 if (!client) {
1263 return std::unique_ptr<InspectorSession> {};
1264 }
1265
1266 CHECK_NOT_NULL(client);
1267
1268 int sessionId = client->ConnectFrontend(std::move(delegate), preventShutdown);
1269 return std::make_unique<SameThreadInspectorSession>(sessionId, client);
1270 }
1271
WaitForDisconnect()1272 void Agent::WaitForDisconnect()
1273 {
1274 CHECK_NOT_NULL(client);
1275 if (client->HasConnectedSessions()) {
1276 if (fprintf(stderr, "Waiting for the debugger to disconnect...\n") < 0) {
1277 return;
1278 }
1279 if (fflush(stderr) != 0) {
1280 return;
1281 }
1282 }
1283
1284 client->ContextDestroyed(parentEnv->context());
1285
1286 if (io != nullptr) {
1287 io->StopAcceptingNewConnections();
1288 client->WaitForSessionsDisconnect();
1289 }
1290 }
1291
PauseOnNextJavascriptStatement(const std::string & reason)1292 void Agent::PauseOnNextJavascriptStatement(const std::string& reason)
1293 {
1294 client->SchedulePauseOnNextStatement(reason);
1295 }
1296
IsActive()1297 bool Agent::IsActive()
1298 {
1299 if (client == nullptr) {
1300 return false;
1301 }
1302 return io != nullptr || client->IsActive();
1303 }
1304
WaitForConnect()1305 void Agent::WaitForConnect()
1306 {
1307 CHECK_NOT_NULL(client);
1308 client->WaitForFrontend();
1309 }
1310
~SameThreadInspectorSession()1311 SameThreadInspectorSession::~SameThreadInspectorSession()
1312 {
1313 auto clientLock = client.lock();
1314 if (clientLock) {
1315 clientLock->DisconnectFrontend(sessionId);
1316 }
1317 }
1318
Dispatch(const v8_inspector::StringView & message)1319 void SameThreadInspectorSession::Dispatch(const v8_inspector::StringView& message)
1320 {
1321 auto clientLock = client.lock();
1322 if (clientLock) {
1323 clientLock->DispatchMessageFromFrontend(sessionId, message);
1324 }
1325 }
1326
1327 } // namespace v8impl
1328
New(JSVM_Env env)1329 jsvm::InspectorAgent* jsvm::InspectorAgent::New(JSVM_Env env)
1330 {
1331 return new v8impl::Agent(env);
1332 }
1333