EventLoop.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  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 <LibJS/Runtime/VM.h>
  9. #include <LibWeb/Bindings/MainThreadVM.h>
  10. #include <LibWeb/DOM/Document.h>
  11. #include <LibWeb/HTML/BrowsingContext.h>
  12. #include <LibWeb/HTML/EventLoop/EventLoop.h>
  13. #include <LibWeb/HTML/Scripting/Environments.h>
  14. #include <LibWeb/HTML/TraversableNavigable.h>
  15. #include <LibWeb/HTML/Window.h>
  16. #include <LibWeb/HighResolutionTime/Performance.h>
  17. #include <LibWeb/HighResolutionTime/TimeOrigin.h>
  18. #include <LibWeb/Page/Page.h>
  19. #include <LibWeb/Platform/EventLoopPlugin.h>
  20. #include <LibWeb/Platform/Timer.h>
  21. namespace Web::HTML {
  22. JS_DEFINE_ALLOCATOR(EventLoop);
  23. EventLoop::EventLoop(Type type)
  24. : m_type(type)
  25. {
  26. m_task_queue = heap().allocate_without_realm<TaskQueue>(*this);
  27. m_microtask_queue = heap().allocate_without_realm<TaskQueue>(*this);
  28. }
  29. EventLoop::~EventLoop() = default;
  30. void EventLoop::visit_edges(Visitor& visitor)
  31. {
  32. Base::visit_edges(visitor);
  33. visitor.visit(m_task_queue);
  34. visitor.visit(m_microtask_queue);
  35. visitor.visit(m_currently_running_task);
  36. visitor.visit(m_backup_incumbent_settings_object_stack);
  37. }
  38. void EventLoop::schedule()
  39. {
  40. if (!m_system_event_loop_timer) {
  41. m_system_event_loop_timer = Platform::Timer::create_single_shot(0, [this] {
  42. process();
  43. });
  44. }
  45. if (!m_system_event_loop_timer->is_active())
  46. m_system_event_loop_timer->restart();
  47. }
  48. EventLoop& main_thread_event_loop()
  49. {
  50. return *static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->event_loop;
  51. }
  52. // https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
  53. void EventLoop::spin_until(JS::SafeFunction<bool()> goal_condition)
  54. {
  55. // FIXME: The spec wants us to do the rest of the enclosing algorithm (i.e. the caller)
  56. // in the context of the currently running task on entry. That's not possible with this implementation.
  57. // 1. Let task be the event loop's currently running task.
  58. // 2. Let task source be task's source.
  59. // 3. Let old stack be a copy of the JavaScript execution context stack.
  60. // 4. Empty the JavaScript execution context stack.
  61. auto& vm = this->vm();
  62. vm.save_execution_context_stack();
  63. vm.clear_execution_context_stack();
  64. // 5. Perform a microtask checkpoint.
  65. perform_a_microtask_checkpoint();
  66. // 6. In parallel:
  67. // 1. Wait until the condition goal is met.
  68. // 2. Queue a task on task source to:
  69. // 1. Replace the JavaScript execution context stack with old stack.
  70. // 2. Perform any steps that appear after this spin the event loop instance in the original algorithm.
  71. // NOTE: This is achieved by returning from the function.
  72. Platform::EventLoopPlugin::the().spin_until([&] {
  73. if (goal_condition())
  74. return true;
  75. if (m_task_queue->has_runnable_tasks()) {
  76. schedule();
  77. // FIXME: Remove the platform event loop plugin so that this doesn't look out of place
  78. Core::EventLoop::current().wake();
  79. }
  80. return goal_condition();
  81. });
  82. vm.restore_execution_context_stack();
  83. // 7. Stop task, allowing whatever algorithm that invoked it to resume.
  84. // NOTE: This is achieved by returning from the function.
  85. }
  86. void EventLoop::spin_processing_tasks_with_source_until(Task::Source source, JS::SafeFunction<bool()> goal_condition)
  87. {
  88. auto& vm = this->vm();
  89. vm.save_execution_context_stack();
  90. vm.clear_execution_context_stack();
  91. perform_a_microtask_checkpoint();
  92. // NOTE: HTML event loop processing steps could run a task with arbitrary source
  93. m_skip_event_loop_processing_steps = true;
  94. Platform::EventLoopPlugin::the().spin_until([&] {
  95. if (goal_condition())
  96. return true;
  97. if (m_task_queue->has_runnable_tasks()) {
  98. auto tasks = m_task_queue->take_tasks_matching([&](auto& task) {
  99. return task.source() == source && task.is_runnable();
  100. });
  101. for (auto& task : tasks) {
  102. m_currently_running_task = task.ptr();
  103. task->execute();
  104. m_currently_running_task = nullptr;
  105. }
  106. }
  107. // FIXME: Remove the platform event loop plugin so that this doesn't look out of place
  108. Core::EventLoop::current().wake();
  109. return goal_condition();
  110. });
  111. m_skip_event_loop_processing_steps = false;
  112. schedule();
  113. vm.restore_execution_context_stack();
  114. }
  115. // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
  116. void EventLoop::process()
  117. {
  118. if (m_skip_event_loop_processing_steps)
  119. return;
  120. // An event loop must continually run through the following steps for as long as it exists:
  121. // 1. Let oldestTask be null.
  122. JS::GCPtr<Task> oldest_task;
  123. // 2. Set taskStartTime to the unsafe shared current time.
  124. double task_start_time = HighResolutionTime::unsafe_shared_current_time();
  125. // 3. Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner,
  126. // with the constraint that the chosen task queue must contain at least one runnable task.
  127. // If there is no such task queue, then jump to the microtasks step below.
  128. auto& task_queue = *m_task_queue;
  129. // 4. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue.
  130. oldest_task = task_queue.take_first_runnable();
  131. if (oldest_task) {
  132. // 5. Set the event loop's currently running task to oldestTask.
  133. m_currently_running_task = oldest_task.ptr();
  134. // 6. Perform oldestTask's steps.
  135. oldest_task->execute();
  136. // 7. Set the event loop's currently running task back to null.
  137. m_currently_running_task = nullptr;
  138. }
  139. // 8. Microtasks: Perform a microtask checkpoint.
  140. perform_a_microtask_checkpoint();
  141. // 9. Let hasARenderingOpportunity be false.
  142. [[maybe_unused]] bool has_a_rendering_opportunity = false;
  143. // FIXME: 10. Let now be the current high resolution time. [HRT]
  144. // FIXME: 11. If oldestTask is not null, then:
  145. // FIXME: 1. Let top-level browsing contexts be an empty set.
  146. // 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.
  147. // FIXME: 3. Report long tasks, passing in taskStartTime, now (the end time of the task), top-level browsing contexts, and oldestTask.
  148. // FIXME: 12. Update the rendering: if this is a window event loop, then:
  149. // 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:
  150. // - Any Document B whose browsing context's container document is A must be listed after A in the list.
  151. // - 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.
  152. // FIXME: NOTE: The sort order specified above is missing here!
  153. Vector<JS::Handle<DOM::Document>> docs = documents_in_this_event_loop();
  154. auto for_each_fully_active_document_in_docs = [&](auto&& callback) {
  155. for (auto& document : docs) {
  156. if (document->is_fully_active())
  157. callback(*document);
  158. }
  159. };
  160. // AD-HOC: Since event loop processing steps do not constantly running in parallel, and
  161. // something must trigger them, we need to manually schedule a repaint for all
  162. // navigables that do not have a rendering opportunity at this event loop iteration.
  163. // Otherwise their repaint will be delayed until something else will trigger event
  164. // loop processing.
  165. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  166. auto navigable = document.navigable();
  167. if (navigable && !navigable->has_a_rendering_opportunity() && navigable->needs_repaint())
  168. schedule();
  169. if (navigable && navigable->has_a_rendering_opportunity())
  170. return;
  171. auto* browsing_context = document.browsing_context();
  172. if (!browsing_context)
  173. return;
  174. auto& page = browsing_context->page();
  175. page.client().schedule_repaint();
  176. });
  177. // 2. Rendering opportunities: Remove from docs all Document objects whose node navigables do not have a rendering opportunity.
  178. docs.remove_all_matching([&](auto& document) {
  179. auto navigable = document->navigable();
  180. return navigable && !navigable->has_a_rendering_opportunity();
  181. });
  182. // 3. If docs is not empty, then set hasARenderingOpportunity to true
  183. // and set this event loop's last render opportunity time to taskStartTime.
  184. if (!docs.is_empty()) {
  185. has_a_rendering_opportunity = true;
  186. m_last_render_opportunity_time = task_start_time;
  187. }
  188. // FIXME: 4. Unnecessary rendering: Remove from docs all Document objects which meet both of the following conditions:
  189. // - The user agent believes that updating the rendering of the Document's browsing context would have no visible effect, and
  190. // - The Document's map of animation frame callbacks is empty.
  191. // https://www.w3.org/TR/intersection-observer/#pending-initial-observation
  192. // In the HTML Event Loops Processing Model, under the "Update the rendering" step, the "Unnecessary rendering" step should be
  193. // modified to add an additional requirement for skipping the rendering update:
  194. // - The document does not have pending initial IntersectionObserver targets.
  195. // 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.
  196. // 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.
  197. // 7. For each fully active Document in docs, run the resize steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
  198. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  199. document.run_the_resize_steps();
  200. });
  201. // 8. For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
  202. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  203. document.run_the_scroll_steps();
  204. });
  205. // 9. For each fully active Document in docs, evaluate media queries and report changes for that Document, passing in now as the timestamp. [CSSOMVIEW]
  206. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  207. document.evaluate_media_queries_and_report_changes();
  208. });
  209. // 10. For each fully active Document in docs, update animations and send events for that Document, passing in now as the timestamp. [WEBANIMATIONS]
  210. // Note: This is handled by the document's animation timer, however, if a document has any requestAnimationFrame callbacks, we need
  211. // to dispatch events before that happens below. Not dispatching here would be observable.
  212. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  213. if (document.window()->animation_frame_callback_driver().has_callbacks()) {
  214. document.update_animations_and_send_events(document.window()->performance()->now());
  215. }
  216. });
  217. // FIXME: 11. For each fully active Document in docs, run the fullscreen steps for that Document, passing in now as the timestamp. [FULLSCREEN]
  218. // 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:
  219. // FIXME: 13. For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp.
  220. auto now = HighResolutionTime::unsafe_shared_current_time();
  221. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  222. run_animation_frame_callbacks(document, now);
  223. });
  224. // FIXME: This step is implemented following the latest specification, while the rest of this method uses an outdated spec.
  225. // NOTE: Gathering and broadcasting of resize observations need to happen after evaluating media queries but before
  226. // updating intersection observations steps.
  227. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  228. // 1. Let resizeObserverDepth be 0.
  229. size_t resize_observer_depth = 0;
  230. // 2. While true:
  231. while (true) {
  232. // 1. Recalculate styles and update layout for doc.
  233. // NOTE: Recalculation of styles is handled by update_layout()
  234. document.update_layout();
  235. // FIXME: 2. Let hadInitialVisibleContentVisibilityDetermination be false.
  236. // FIXME: 3. For each element element with 'auto' used value of 'content-visibility':
  237. // FIXME: 4. If hadInitialVisibleContentVisibilityDetermination is true, then continue.
  238. // 5. Gather active resize observations at depth resizeObserverDepth for doc.
  239. document.gather_active_observations_at_depth(resize_observer_depth);
  240. // 6. If doc has active resize observations:
  241. if (document.has_active_resize_observations()) {
  242. // 1. Set resizeObserverDepth to the result of broadcasting active resize observations given doc.
  243. resize_observer_depth = document.broadcast_active_resize_observations();
  244. // 2. Continue.
  245. continue;
  246. }
  247. // 7. Otherwise, break.
  248. break;
  249. }
  250. // 3. If doc has skipped resize observations, then deliver resize loop error given doc.
  251. if (document.has_skipped_resize_observations()) {
  252. // FIXME: Deliver resize loop error.
  253. }
  254. });
  255. // 14. For each fully active Document in docs, run the update intersection observations steps for that Document, passing in now as the timestamp. [INTERSECTIONOBSERVER]
  256. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  257. document.run_the_update_intersection_observations_steps(now);
  258. });
  259. // FIXME: 15. Invoke the mark paint timing algorithm for each Document object in docs.
  260. // 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.
  261. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  262. auto navigable = document.navigable();
  263. if (navigable && navigable->needs_repaint()) {
  264. auto* browsing_context = document.browsing_context();
  265. auto& page = browsing_context->page();
  266. if (navigable->is_traversable()) {
  267. VERIFY(page.client().is_ready_to_paint());
  268. page.client().paint_next_frame();
  269. }
  270. }
  271. });
  272. // FIXME: Not in the spec: If there is a screenshot request queued, process it now.
  273. // This prevents tests deadlocking on screenshot requests on macOS.
  274. for (auto& document : docs) {
  275. if (document->page().top_level_traversable()->needs_repaint())
  276. document->page().client().process_screenshot_requests();
  277. }
  278. // 13. If all of the following are true
  279. // - this is a window event loop
  280. // - there is no task in this event loop's task queues whose document is fully active
  281. // - this event loop's microtask queue is empty
  282. // - hasARenderingOpportunity is false
  283. // FIXME: has_a_rendering_opportunity is always true
  284. if (m_type == Type::Window && !task_queue.has_runnable_tasks() && m_microtask_queue->is_empty() /*&& !has_a_rendering_opportunity*/) {
  285. // 1. Set this event loop's last idle period start time to the unsafe shared current time.
  286. m_last_idle_period_start_time = HighResolutionTime::unsafe_shared_current_time();
  287. // 2. Let computeDeadline be the following steps:
  288. // NOTE: instead of passing around a function we use this event loop, which has compute_deadline()
  289. // 3. For each win of the same-loop windows for this event loop,
  290. // perform the start an idle period algorithm for win with computeDeadline. [REQUESTIDLECALLBACK]
  291. for (auto& win : same_loop_windows())
  292. win->start_an_idle_period();
  293. }
  294. // FIXME: 14. If this is a worker event loop, then:
  295. // 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:
  296. // FIXME: 1. Let now be the current high resolution time. [HRT]
  297. // FIXME: 2. Run the animation frame callbacks for that DedicatedWorkerGlobalScope, passing in now as the timestamp.
  298. // FIXME: 3. Update the rendering of that dedicated worker to reflect the current state.
  299. // 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.
  300. // If there are eligible tasks in the queue, schedule a new round of processing. :^)
  301. if (m_task_queue->has_runnable_tasks() || (!m_microtask_queue->is_empty() && !m_performing_a_microtask_checkpoint))
  302. schedule();
  303. // For each doc of docs, process top layer removals given doc.
  304. for_each_fully_active_document_in_docs([&](DOM::Document& document) {
  305. document.process_top_layer_removals();
  306. });
  307. }
  308. // https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task
  309. TaskID queue_a_task(HTML::Task::Source source, JS::GCPtr<EventLoop> event_loop, JS::GCPtr<DOM::Document> document, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
  310. {
  311. // 1. If event loop was not given, set event loop to the implied event loop.
  312. if (!event_loop)
  313. event_loop = main_thread_event_loop();
  314. // FIXME: 2. If document was not given, set document to the implied document.
  315. // 3. Let task be a new task.
  316. // 4. Set task's steps to steps.
  317. // 5. Set task's source to source.
  318. // 6. Set task's document to the document.
  319. // 7. Set task's script evaluation environment settings object set to an empty set.
  320. auto task = HTML::Task::create(event_loop->vm(), source, document, steps);
  321. // 8. Let queue be the task queue to which source is associated on event loop.
  322. auto& queue = source == HTML::Task::Source::Microtask ? event_loop->microtask_queue() : event_loop->task_queue();
  323. // 9. Append task to queue.
  324. queue.add(task);
  325. return queue.last_added_task()->id();
  326. }
  327. // https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task
  328. TaskID queue_global_task(HTML::Task::Source source, JS::Object& global_object, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
  329. {
  330. // 1. Let event loop be global's relevant agent's event loop.
  331. auto& global_custom_data = verify_cast<Bindings::WebEngineCustomData>(*global_object.vm().custom_data());
  332. auto& event_loop = global_custom_data.event_loop;
  333. // 2. Let document be global's associated Document, if global is a Window object; otherwise null.
  334. DOM::Document* document { nullptr };
  335. if (is<HTML::Window>(global_object)) {
  336. auto& window_object = verify_cast<HTML::Window>(global_object);
  337. document = &window_object.associated_document();
  338. }
  339. // 3. Queue a task given source, event loop, document, and steps.
  340. return queue_a_task(source, *event_loop, document, steps);
  341. }
  342. // https://html.spec.whatwg.org/#queue-a-microtask
  343. void queue_a_microtask(DOM::Document const* document, JS::NonnullGCPtr<JS::HeapFunction<void()>> steps)
  344. {
  345. // 1. If event loop was not given, set event loop to the implied event loop.
  346. auto& event_loop = HTML::main_thread_event_loop();
  347. // FIXME: 2. If document was not given, set document to the implied document.
  348. // 3. Let microtask be a new task.
  349. // 4. Set microtask's steps to steps.
  350. // 5. Set microtask's source to the microtask task source.
  351. // 6. Set microtask's document to document.
  352. auto& vm = event_loop.vm();
  353. auto microtask = HTML::Task::create(vm, HTML::Task::Source::Microtask, document, steps);
  354. // FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set.
  355. // 8. Enqueue microtask on event loop's microtask queue.
  356. event_loop.microtask_queue().enqueue(microtask);
  357. }
  358. void perform_a_microtask_checkpoint()
  359. {
  360. main_thread_event_loop().perform_a_microtask_checkpoint();
  361. }
  362. // https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
  363. void EventLoop::perform_a_microtask_checkpoint()
  364. {
  365. // 1. If the event loop's performing a microtask checkpoint is true, then return.
  366. if (m_performing_a_microtask_checkpoint)
  367. return;
  368. // 2. Set the event loop's performing a microtask checkpoint to true.
  369. m_performing_a_microtask_checkpoint = true;
  370. // 3. While the event loop's microtask queue is not empty:
  371. while (!m_microtask_queue->is_empty()) {
  372. // 1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.
  373. auto oldest_microtask = m_microtask_queue->dequeue();
  374. // 2. Set the event loop's currently running task to oldestMicrotask.
  375. m_currently_running_task = oldest_microtask;
  376. // 3. Run oldestMicrotask.
  377. oldest_microtask->execute();
  378. // 4. Set the event loop's currently running task back to null.
  379. m_currently_running_task = nullptr;
  380. }
  381. // 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object.
  382. for (auto& environment_settings_object : m_related_environment_settings_objects)
  383. environment_settings_object->notify_about_rejected_promises({});
  384. // FIXME: 5. Cleanup Indexed Database transactions.
  385. // 6. Perform ClearKeptObjects().
  386. vm().finish_execution_generation();
  387. // 7. Set the event loop's performing a microtask checkpoint to false.
  388. m_performing_a_microtask_checkpoint = false;
  389. }
  390. Vector<JS::Handle<DOM::Document>> EventLoop::documents_in_this_event_loop() const
  391. {
  392. Vector<JS::Handle<DOM::Document>> documents;
  393. for (auto& document : m_documents) {
  394. VERIFY(document);
  395. if (document->is_decoded_svg())
  396. continue;
  397. documents.append(JS::make_handle(*document));
  398. }
  399. return documents;
  400. }
  401. void EventLoop::register_document(Badge<DOM::Document>, DOM::Document& document)
  402. {
  403. m_documents.append(&document);
  404. }
  405. void EventLoop::unregister_document(Badge<DOM::Document>, DOM::Document& document)
  406. {
  407. bool did_remove = m_documents.remove_first_matching([&](auto& entry) { return entry.ptr() == &document; });
  408. VERIFY(did_remove);
  409. }
  410. void EventLoop::push_onto_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
  411. {
  412. m_backup_incumbent_settings_object_stack.append(environment_settings_object);
  413. }
  414. void EventLoop::pop_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>)
  415. {
  416. m_backup_incumbent_settings_object_stack.take_last();
  417. }
  418. EnvironmentSettingsObject& EventLoop::top_of_backup_incumbent_settings_object_stack()
  419. {
  420. return m_backup_incumbent_settings_object_stack.last();
  421. }
  422. void EventLoop::register_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
  423. {
  424. m_related_environment_settings_objects.append(&environment_settings_object);
  425. }
  426. void EventLoop::unregister_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
  427. {
  428. bool did_remove = m_related_environment_settings_objects.remove_first_matching([&](auto& entry) { return entry == &environment_settings_object; });
  429. VERIFY(did_remove);
  430. }
  431. // https://html.spec.whatwg.org/multipage/webappapis.html#same-loop-windows
  432. Vector<JS::Handle<HTML::Window>> EventLoop::same_loop_windows() const
  433. {
  434. Vector<JS::Handle<HTML::Window>> windows;
  435. for (auto& document : documents_in_this_event_loop()) {
  436. if (document->is_fully_active())
  437. windows.append(JS::make_handle(document->window()));
  438. }
  439. return windows;
  440. }
  441. // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model:last-idle-period-start-time
  442. double EventLoop::compute_deadline() const
  443. {
  444. // 1. Let deadline be this event loop's last idle period start time plus 50.
  445. auto deadline = m_last_idle_period_start_time + 50;
  446. // 2. Let hasPendingRenders be false.
  447. auto has_pending_renders = false;
  448. // 3. For each windowInSameLoop of the same-loop windows for this event loop:
  449. for (auto& window : same_loop_windows()) {
  450. // 1. If windowInSameLoop's map of animation frame callbacks is not empty,
  451. // or if the user agent believes that the windowInSameLoop might have pending rendering updates,
  452. // set hasPendingRenders to true.
  453. if (window->has_animation_frame_callbacks())
  454. has_pending_renders = true;
  455. // FIXME: 2. Let timerCallbackEstimates be the result of getting the values of windowInSameLoop's map of active timers.
  456. // FIXME: 3. For each timeoutDeadline of timerCallbackEstimates, if timeoutDeadline is less than deadline, set deadline to timeoutDeadline.
  457. }
  458. // 4. If hasPendingRenders is true, then:
  459. if (has_pending_renders) {
  460. // 1. Let nextRenderDeadline be this event loop's last render opportunity time plus (1000 divided by the current refresh rate).
  461. // FIXME: Hardcoded to 60Hz
  462. auto next_render_deadline = m_last_render_opportunity_time + (1000.0 / 60.0);
  463. // 2. If nextRenderDeadline is less than deadline, then return nextRenderDeadline.
  464. if (next_render_deadline < deadline)
  465. return next_render_deadline;
  466. }
  467. // 5. Return deadline.
  468. return deadline;
  469. }
  470. }