/* * Copyright (c) 2024, the Ladybird developers. * Copyright (c) 2024, Felipe Muñoz Mazur * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { GC_DEFINE_ALLOCATOR(CloseWatcher); // https://html.spec.whatwg.org/multipage/interaction.html#establish-a-close-watcher GC::Ref CloseWatcher::establish(HTML::Window& window) { // 1. Assert: window's associated Document is fully active. VERIFY(window.associated_document().is_fully_active()); // 2. Let closeWatcher be a new close watcher auto close_watcher = window.realm().create(window.realm()); // 3. Let manager be window's associated close watcher manager auto manager = window.close_watcher_manager(); // 4 - 6. Moved to CloseWatcherManager::add manager->add(close_watcher); // 7. Return close_watcher. return close_watcher; } // https://html.spec.whatwg.org/multipage/interaction.html#dom-closewatcher WebIDL::ExceptionOr> CloseWatcher::construct_impl(JS::Realm& realm, CloseWatcherOptions const& options) { auto& window = verify_cast(realm.global_object()); // NOTE: Not in spec explicitly, but this should account for detached iframes too. See /close-watcher/frame-removal.html WPT. auto navigable = window.navigable(); if (navigable && navigable->has_been_destroyed()) return WebIDL::InvalidStateError::create(realm, "The iframe has been detached"_string); // 1. If this's relevant global object's associated Document is not fully active, then return an "InvalidStateError" DOMException. if (!window.associated_document().is_fully_active()) return WebIDL::InvalidStateError::create(realm, "The document is not fully active."_string); // 2. Let close_watcher be the result of establishing a close watcher auto close_watcher = establish(window); // 3. If options["signal"] exists, then: if (auto signal = options.signal) { // 3.1 If options["signal"]'s aborted, then destroy closeWatcher. if (signal->aborted()) { close_watcher->destroy(); } // 3.2 Add the following steps to options["signal"]: signal->add_abort_algorithm([close_watcher] { // 3.2.1 Destroy closeWatcher. close_watcher->destroy(); }); } return close_watcher; } CloseWatcher::CloseWatcher(JS::Realm& realm) : DOM::EventTarget(realm) { } // https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close bool CloseWatcher::request_close() { // 1. If closeWatcher is not active, then return. if (!m_is_active) return true; // 2. If closeWatcher's is running cancel action is true, then return true. if (m_is_running_cancel_action) return true; // 3. Let window be closeWatcher's window. auto& window = verify_cast(realm().global_object()); // 4. If window's associated Document is not fully active, then return true. if (!window.associated_document().is_fully_active()) return true; // 5. Let canPreventClose be true if window's close watcher manager's groups's size is less than window's close watcher manager's allowed number of groups, // and window has history-action activation; otherwise false. auto manager = window.close_watcher_manager(); bool can_prevent_close = manager->can_prevent_close() && window.has_history_action_activation(); // 6. Set closeWatcher's is running cancel action to true. m_is_running_cancel_action = true; // 7. Let shouldContinue be the result of running closeWatcher's cancel action given canPreventClose. bool should_continue = dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .cancelable = can_prevent_close })); // 8. Set closeWatcher's is running cancel action to false. m_is_running_cancel_action = false; // 9. If shouldContinue is false, then: if (!should_continue) { // 9.1 Assert: canPreventClose is true. VERIFY(can_prevent_close); // 9.2 Consume history-action user activation given window. window.consume_history_action_user_activation(); return false; } // 10. Close closeWatcher. close(); // 11. Return true. return true; } // https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-close void CloseWatcher::close() { // 1. If closeWatcher is not active, then return. if (!m_is_active) return; // 2. If closeWatcher's window's associated Document is not fully active, then return. if (!verify_cast(realm().global_object()).associated_document().is_fully_active()) return; // 3. Destroy closeWatcher. destroy(); // 4. Run closeWatcher's close action. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::close)); } // https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-destroy void CloseWatcher::destroy() { // 1. Let manager be closeWatcher's window's close watcher manager. auto manager = verify_cast(realm().global_object()).close_watcher_manager(); // 2-3. Moved to CloseWatcherManager::remove manager->remove(*this); m_is_active = false; } void CloseWatcher::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(CloseWatcher); } void CloseWatcher::set_oncancel(WebIDL::CallbackType* event_handler) { set_event_handler_attribute(HTML::EventNames::cancel, event_handler); } WebIDL::CallbackType* CloseWatcher::oncancel() { return event_handler_attribute(HTML::EventNames::cancel); } void CloseWatcher::set_onclose(WebIDL::CallbackType* event_handler) { set_event_handler_attribute(HTML::EventNames::close, event_handler); } WebIDL::CallbackType* CloseWatcher::onclose() { return event_handler_attribute(HTML::EventNames::close); } }