EventLoop.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibCore/EventLoop.h>
  8. #include <LibCore/Timer.h>
  9. #include <LibJS/Runtime/VM.h>
  10. #include <LibWeb/Bindings/MainThreadVM.h>
  11. #include <LibWeb/DOM/Document.h>
  12. #include <LibWeb/HTML/BrowsingContext.h>
  13. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  14. #include <LibWeb/HTML/Scripting/Environments.h>
  15. #include <LibWeb/HTML/Window.h>
  16. #include <LibWeb/HighResolutionTime/Performance.h>
  17. namespace Web::HTML {
  18. EventLoop::EventLoop()
  19. : m_task_queue(*this)
  20. , m_microtask_queue(*this)
  21. {
  22. }
  23. EventLoop::~EventLoop() = default;
  24. void EventLoop::schedule()
  25. {
  26. if (!m_system_event_loop_timer) {
  27. m_system_event_loop_timer = Core::Timer::create_single_shot(0, [this] {
  28. process();
  29. });
  30. }
  31. if (!m_system_event_loop_timer->is_active())
  32. m_system_event_loop_timer->restart();
  33. }
  34. void EventLoop::set_vm(JS::VM& vm)
  35. {
  36. VERIFY(!m_vm);
  37. m_vm = &vm;
  38. }
  39. EventLoop& main_thread_event_loop()
  40. {
  41. return static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->event_loop;
  42. }
  43. // https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
  44. void EventLoop::spin_until(Function<bool()> goal_condition)
  45. {
  46. // FIXME: 1. Let task be the event loop's currently running task.
  47. // FIXME: 2. Let task source be task's source.
  48. // 3. Let old stack be a copy of the JavaScript execution context stack.
  49. // 4. Empty the JavaScript execution context stack.
  50. auto& vm = Bindings::main_thread_vm();
  51. vm.save_execution_context_stack();
  52. // 5. Perform a microtask checkpoint.
  53. perform_a_microtask_checkpoint();
  54. // 6. In parallel:
  55. // NOTE: We do these in reverse order here, but it shouldn't matter.
  56. // 2. Queue a task on task source to:
  57. // 1. Replace the JavaScript execution context stack with old stack.
  58. vm.restore_execution_context_stack();
  59. // 2. Perform any steps that appear after this spin the event loop instance in the original algorithm.
  60. // NOTE: This is achieved by returning from the function.
  61. // 1. Wait until the condition goal is met.
  62. Core::EventLoop loop;
  63. loop.spin_until([&]() -> bool {
  64. if (goal_condition())
  65. return true;
  66. return goal_condition();
  67. });
  68. // 7. Stop task, allowing whatever algorithm that invoked it to resume.
  69. // NOTE: This is achieved by returning from the function.
  70. }
  71. // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
  72. void EventLoop::process()
  73. {
  74. // An event loop must continually run through the following steps for as long as it exists:
  75. // 1. Let oldestTask be null.
  76. OwnPtr<Task> oldest_task;
  77. // 2. Let taskStartTime be the current high resolution time.
  78. // FIXME: 'current high resolution time' in hr-time-3 takes a global object,
  79. // the HTML spec has not been updated to reflect this, let's use the shared timer.
  80. // - https://github.com/whatwg/html/issues/7776
  81. double task_start_time = unsafe_shared_current_time();
  82. // 3. Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner,
  83. // with the constraint that the chosen task queue must contain at least one runnable task.
  84. // If there is no such task queue, then jump to the microtasks step below.
  85. auto& task_queue = m_task_queue;
  86. // 4. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue.
  87. oldest_task = task_queue.take_first_runnable();
  88. if (oldest_task) {
  89. // 5. Set the event loop's currently running task to oldestTask.
  90. m_currently_running_task = oldest_task.ptr();
  91. // 6. Perform oldestTask's steps.
  92. oldest_task->execute();
  93. // 7. Set the event loop's currently running task back to null.
  94. m_currently_running_task = nullptr;
  95. }
  96. // 8. Microtasks: Perform a microtask checkpoint.
  97. perform_a_microtask_checkpoint();
  98. // 9. Let hasARenderingOpportunity be false.
  99. [[maybe_unused]] bool has_a_rendering_opportunity = false;
  100. // FIXME: 10. Let now be the current high resolution time. [HRT]
  101. // FIXME: 11. If oldestTask is not null, then:
  102. // FIXME: 1. Let top-level browsing contexts be an empty set.
  103. // FIXME: 2. For each environment settings object settings of oldestTask's script evaluation environment settings object set, append setting's top-level browsing context to top-level browsing contexts.
  104. // FIXME: 3. Report long tasks, passing in taskStartTime, now (the end time of the task), top-level browsing contexts, and oldestTask.
  105. // FIXME: 12. Update the rendering: if this is a window event loop, then:
  106. // FIXME: 1. Let docs be all Document objects whose relevant agent's event loop is this event loop, sorted arbitrarily except that the following conditions must be met:
  107. // - Any Document B whose browsing context's container document is A must be listed after A in the list.
  108. // - If there are two documents A and B whose browsing contexts are both child browsing contexts whose container documents are another Document C, then the order of A and B in the list must match the shadow-including tree order of their respective browsing context containers in C's node tree.
  109. // FIXME: NOTE: The sort order specified above is missing here!
  110. Vector<JS::Handle<DOM::Document>> docs = documents_in_this_event_loop();
  111. auto for_each_fully_active_document_in_docs = [&](auto&& callback) {
  112. for (auto& document : docs) {
  113. if (document->is_fully_active())
  114. callback(*document);
  115. }
  116. };
  117. // 2. Rendering opportunities: Remove from docs all Document objects whose browsing context do not have a rendering opportunity.
  118. docs.remove_all_matching([&](auto& document) {
  119. return document->browsing_context() && !document->browsing_context()->has_a_rendering_opportunity();
  120. });
  121. // 3. If docs is not empty, then set hasARenderingOpportunity to true
  122. // and set this event loop's last render opportunity time to taskStartTime.
  123. if (!docs.is_empty()) {
  124. has_a_rendering_opportunity = true;
  125. m_last_render_opportunity_time = task_start_time;
  126. }
  127. // FIXME: 4. Unnecessary rendering: Remove from docs all Document objects which meet both of the following conditions:
  128. // - The user agent believes that updating the rendering of the Document's browsing context would have no visible effect, and
  129. // - The Document's map of animation frame callbacks is empty.
  130. // FIXME: 5. Remove from docs all Document objects for which the user agent believes that it's preferable to skip updating the rendering for other reasons.
  131. // FIXME: 6. For each fully active Document in docs, flush autofocus candidates for that Document if its browsing context is a top-level browsing context.
  132. // 7. For each fully active Document in docs, run the resize steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
  133. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  134. document.run_the_resize_steps();
  135. });
  136. // FIXME: 8. For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
  137. // 9. For each fully active Document in docs, evaluate media queries and report changes for that Document, passing in now as the timestamp. [CSSOMVIEW]
  138. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  139. document.evaluate_media_queries_and_report_changes();
  140. });
  141. // FIXME: 10. For each fully active Document in docs, update animations and send events for that Document, passing in now as the timestamp. [WEBANIMATIONS]
  142. // FIXME: 11. For each fully active Document in docs, run the fullscreen steps for that Document, passing in now as the timestamp. [FULLSCREEN]
  143. // FIXME: 12. For each fully active Document in docs, if the user agent detects that the backing storage associated with a CanvasRenderingContext2D or an OffscreenCanvasRenderingContext2D, context, has been lost, then it must run the context lost steps for each such context:
  144. // FIXME: 13. For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp.
  145. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  146. run_animation_frame_callbacks(document, document.window().performance().now());
  147. });
  148. // FIXME: 14. For each fully active Document in docs, run the update intersection observations steps for that Document, passing in now as the timestamp. [INTERSECTIONOBSERVER]
  149. // FIXME: 15. Invoke the mark paint timing algorithm for each Document object in docs.
  150. // FIXME: 16. For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state.
  151. // 13. If all of the following are true
  152. // - this is a window event loop
  153. // - there is no task in this event loop's task queues whose document is fully active
  154. // - this event loop's microtask queue is empty
  155. // - hasARenderingOpportunity is false
  156. // FIXME: has_a_rendering_opportunity is always true
  157. if (m_type == Type::Window && !task_queue.has_runnable_tasks() && m_microtask_queue.is_empty() /*&& !has_a_rendering_opportunity*/) {
  158. // 1. Set this event loop's last idle period start time to the current high resolution time.
  159. m_last_idle_period_start_time = unsafe_shared_current_time();
  160. // 2. Let computeDeadline be the following steps:
  161. // NOTE: instead of passing around a function we use this event loop, which has compute_deadline()
  162. // 3. For each win of the same-loop windows for this event loop,
  163. // perform the start an idle period algorithm for win with computeDeadline. [REQUESTIDLECALLBACK]
  164. for (auto& win : same_loop_windows())
  165. win->start_an_idle_period();
  166. }
  167. // FIXME: 14. If this is a worker event loop, then:
  168. // FIXME: 1. If this event loop's agent's single realm's global object is a supported DedicatedWorkerGlobalScope and the user agent believes that it would benefit from having its rendering updated at this time, then:
  169. // FIXME: 1. Let now be the current high resolution time. [HRT]
  170. // FIXME: 2. Run the animation frame callbacks for that DedicatedWorkerGlobalScope, passing in now as the timestamp.
  171. // FIXME: 3. Update the rendering of that dedicated worker to reflect the current state.
  172. // FIXME: 2. If there are no tasks in the event loop's task queues and the WorkerGlobalScope object's closing flag is true, then destroy the event loop, aborting these steps, resuming the run a worker steps described in the Web workers section below.
  173. // If there are tasks in the queue, schedule a new round of processing. :^)
  174. if (m_task_queue.has_runnable_tasks() || !m_microtask_queue.is_empty())
  175. schedule();
  176. }
  177. // FIXME: This is here to paper over an issue in the HTML parser where it'll create new interpreters (and thus ESOs) on temporary documents created for innerHTML if it uses Document::realm() to get the global object.
  178. // Use queue_global_task instead.
  179. void old_queue_global_task_with_document(HTML::Task::Source source, DOM::Document& document, Function<void()> steps)
  180. {
  181. main_thread_event_loop().task_queue().add(HTML::Task::create(source, &document, move(steps)));
  182. }
  183. // https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task
  184. void queue_global_task(HTML::Task::Source source, JS::Object& global_object, Function<void()> steps)
  185. {
  186. // 1. Let event loop be global's relevant agent's event loop.
  187. auto& global_custom_data = verify_cast<Bindings::WebEngineCustomData>(*global_object.vm().custom_data());
  188. auto& event_loop = global_custom_data.event_loop;
  189. // 2. Let document be global's associated Document, if global is a Window object; otherwise null.
  190. DOM::Document* document { nullptr };
  191. if (is<HTML::Window>(global_object)) {
  192. auto& window_object = verify_cast<HTML::Window>(global_object);
  193. document = &window_object.impl().associated_document();
  194. }
  195. // 3. Queue a task given source, event loop, document, and steps.
  196. event_loop.task_queue().add(HTML::Task::create(source, document, move(steps)));
  197. }
  198. // https://html.spec.whatwg.org/#queue-a-microtask
  199. void queue_a_microtask(DOM::Document* document, Function<void()> steps)
  200. {
  201. // 1. If event loop was not given, set event loop to the implied event loop.
  202. auto& event_loop = HTML::main_thread_event_loop();
  203. // FIXME: 2. If document was not given, set document to the implied document.
  204. // 3. Let microtask be a new task.
  205. // 4. Set microtask's steps to steps.
  206. // 5. Set microtask's source to the microtask task source.
  207. // 6. Set microtask's document to document.
  208. auto microtask = HTML::Task::create(HTML::Task::Source::Microtask, document, move(steps));
  209. // FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set.
  210. // 8. Enqueue microtask on event loop's microtask queue.
  211. event_loop.microtask_queue().enqueue(move(microtask));
  212. }
  213. void perform_a_microtask_checkpoint()
  214. {
  215. main_thread_event_loop().perform_a_microtask_checkpoint();
  216. }
  217. // https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
  218. void EventLoop::perform_a_microtask_checkpoint()
  219. {
  220. // 1. If the event loop's performing a microtask checkpoint is true, then return.
  221. if (m_performing_a_microtask_checkpoint)
  222. return;
  223. // 2. Set the event loop's performing a microtask checkpoint to true.
  224. m_performing_a_microtask_checkpoint = true;
  225. // 3. While the event loop's microtask queue is not empty:
  226. while (!m_microtask_queue.is_empty()) {
  227. // 1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.
  228. auto oldest_microtask = m_microtask_queue.dequeue();
  229. // 2. Set the event loop's currently running task to oldestMicrotask.
  230. m_currently_running_task = oldest_microtask;
  231. // 3. Run oldestMicrotask.
  232. oldest_microtask->execute();
  233. // 4. Set the event loop's currently running task back to null.
  234. m_currently_running_task = nullptr;
  235. }
  236. // 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object.
  237. for (auto& environment_settings_object : m_related_environment_settings_objects)
  238. environment_settings_object.notify_about_rejected_promises({});
  239. // FIXME: 5. Cleanup Indexed Database transactions.
  240. // 6. Perform ClearKeptObjects().
  241. vm().finish_execution_generation();
  242. // 7. Set the event loop's performing a microtask checkpoint to false.
  243. m_performing_a_microtask_checkpoint = false;
  244. }
  245. Vector<JS::Handle<DOM::Document>> EventLoop::documents_in_this_event_loop() const
  246. {
  247. Vector<JS::Handle<DOM::Document>> documents;
  248. for (auto& document : m_documents) {
  249. VERIFY(document);
  250. documents.append(JS::make_handle(*document.ptr()));
  251. }
  252. return documents;
  253. }
  254. void EventLoop::register_document(Badge<DOM::Document>, DOM::Document& document)
  255. {
  256. m_documents.append(&document);
  257. }
  258. void EventLoop::unregister_document(Badge<DOM::Document>, DOM::Document& document)
  259. {
  260. bool did_remove = m_documents.remove_first_matching([&](auto& entry) { return entry.ptr() == &document; });
  261. VERIFY(did_remove);
  262. }
  263. void EventLoop::push_onto_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
  264. {
  265. m_backup_incumbent_settings_object_stack.append(environment_settings_object);
  266. }
  267. void EventLoop::pop_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>)
  268. {
  269. m_backup_incumbent_settings_object_stack.take_last();
  270. }
  271. EnvironmentSettingsObject& EventLoop::top_of_backup_incumbent_settings_object_stack()
  272. {
  273. return m_backup_incumbent_settings_object_stack.last();
  274. }
  275. void EventLoop::register_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
  276. {
  277. m_related_environment_settings_objects.append(environment_settings_object);
  278. }
  279. void EventLoop::unregister_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
  280. {
  281. bool did_remove = m_related_environment_settings_objects.remove_first_matching([&](auto& entry) { return &entry == &environment_settings_object; });
  282. VERIFY(did_remove);
  283. }
  284. // https://html.spec.whatwg.org/multipage/webappapis.html#same-loop-windows
  285. Vector<JS::Handle<HTML::Window>> EventLoop::same_loop_windows() const
  286. {
  287. Vector<JS::Handle<HTML::Window>> windows;
  288. for (auto& document : documents_in_this_event_loop())
  289. windows.append(JS::make_handle(document->window()));
  290. return windows;
  291. }
  292. // https://w3c.github.io/hr-time/#dfn-unsafe-shared-current-time
  293. double EventLoop::unsafe_shared_current_time() const
  294. {
  295. return Time::now_monotonic().to_nanoseconds() / 1e6;
  296. }
  297. // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model:last-idle-period-start-time
  298. double EventLoop::compute_deadline() const
  299. {
  300. // 1. Let deadline be this event loop's last idle period start time plus 50.
  301. auto deadline = m_last_idle_period_start_time + 50;
  302. // 2. Let hasPendingRenders be false.
  303. auto has_pending_renders = false;
  304. // 3. For each windowInSameLoop of the same-loop windows for this event loop:
  305. for (auto& window : same_loop_windows()) {
  306. // 1. If windowInSameLoop's map of animation frame callbacks is not empty,
  307. // or if the user agent believes that the windowInSameLoop might have pending rendering updates,
  308. // set hasPendingRenders to true.
  309. if (window->has_animation_frame_callbacks())
  310. has_pending_renders = true;
  311. // FIXME: 2. Let timerCallbackEstimates be the result of getting the values of windowInSameLoop's map of active timers.
  312. // FIXME: 3. For each timeoutDeadline of timerCallbackEstimates, if timeoutDeadline is less than deadline, set deadline to timeoutDeadline.
  313. }
  314. // 4. If hasPendingRenders is true, then:
  315. if (has_pending_renders) {
  316. // 1. Let nextRenderDeadline be this event loop's last render opportunity time plus (1000 divided by the current refresh rate).
  317. // FIXME: Hardcoded to 60Hz
  318. auto next_render_deadline = m_last_render_opportunity_time + (1000.0 / 60.0);
  319. // 2. If nextRenderDeadline is less than deadline, then return nextRenderDeadline.
  320. if (next_render_deadline < deadline)
  321. return next_render_deadline;
  322. }
  323. // 5. Return deadline.
  324. return deadline;
  325. }
  326. }