mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 21:08:12 +00:00
LibWeb: Implement <meta http-equiv="Refresh">
Required by Atlassian to continue to their authorization process. Also used by the SerenityOS FAQ redirect on the website, the Bootstrap documentation for going to older versions from the dropdown and likely several other sites.
This commit is contained in:
parent
0e8a0a8191
commit
34c702e6e8
4 changed files with 242 additions and 0 deletions
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibCore/Timer.h>
|
||||
|
@ -77,6 +78,7 @@
|
|||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/WindowProxy.h>
|
||||
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
|
||||
#include <LibWeb/Infra/CharacterTypes.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
#include <LibWeb/IntersectionObserver/IntersectionObserver.h>
|
||||
#include <LibWeb/Layout/BlockFormattingContext.h>
|
||||
|
@ -1756,6 +1758,10 @@ void Document::completely_finish_loading()
|
|||
// 2. Set document's completely loaded time to the current time.
|
||||
m_completely_loaded_time = AK::UnixDateTime::now();
|
||||
|
||||
// NOTE: See the end of shared_declarative_refresh_steps.
|
||||
if (m_active_refresh_timer)
|
||||
m_active_refresh_timer->start();
|
||||
|
||||
// 3. Let container be document's browsing context's container.
|
||||
auto container = JS::make_handle(browsing_context()->container());
|
||||
|
||||
|
@ -3049,4 +3055,162 @@ void Document::start_intersection_observing_a_lazy_loading_element(Element& elem
|
|||
m_lazy_load_intersection_observer->observe(element);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#shared-declarative-refresh-steps
|
||||
void Document::shared_declarative_refresh_steps(StringView input, JS::GCPtr<HTML::HTMLMetaElement const> meta_element)
|
||||
{
|
||||
// 1. If document's will declaratively refresh is true, then return.
|
||||
if (m_will_declaratively_refresh)
|
||||
return;
|
||||
|
||||
// 2. Let position point at the first code point of input.
|
||||
GenericLexer lexer(input);
|
||||
|
||||
// 3. Skip ASCII whitespace within input given position.
|
||||
lexer.ignore_while(Infra::is_ascii_whitespace);
|
||||
|
||||
// 4. Let time be 0.
|
||||
u32 time = 0;
|
||||
|
||||
// 5. Collect a sequence of code points that are ASCII digits from input given position, and let the result be timeString.
|
||||
auto time_string = lexer.consume_while(is_ascii_digit);
|
||||
|
||||
// 6. If timeString is the empty string, then:
|
||||
if (time_string.is_empty()) {
|
||||
// 1. If the code point in input pointed to by position is not U+002E (.), then return.
|
||||
if (lexer.peek() != '.')
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Otherwise, set time to the result of parsing timeString using the rules for parsing non-negative integers.
|
||||
// FIXME: Not sure if this exactly matches the spec's "rules for parsing non-negative integers".
|
||||
auto maybe_time = time_string.to_uint<u32>();
|
||||
|
||||
// FIXME: Since we only collected ASCII digits, this can only fail because of overflow. What do we do when that happens? For now, default to 0.
|
||||
if (maybe_time.has_value() && maybe_time.value() < NumericLimits<int>::max() && !Checked<int>::multiplication_would_overflow(static_cast<int>(maybe_time.value()), 1000)) {
|
||||
time = maybe_time.value();
|
||||
}
|
||||
|
||||
// 8. Collect a sequence of code points that are ASCII digits and U+002E FULL STOP characters (.) from input given
|
||||
// position. Ignore any collected characters.
|
||||
lexer.ignore_while([](auto c) {
|
||||
return is_ascii_digit(c) || c == '.';
|
||||
});
|
||||
|
||||
// 9. Let urlRecord be document's URL.
|
||||
auto url_record = url();
|
||||
|
||||
// 10. If position is not past the end of input, then:
|
||||
if (!lexer.is_eof()) {
|
||||
// 1. If the code point in input pointed to by position is not U+003B (;), U+002C (,), or ASCII whitespace, then return.
|
||||
if (lexer.peek() != ';' && lexer.peek() != ',' && !Infra::is_ascii_whitespace(lexer.peek()))
|
||||
return;
|
||||
|
||||
// 2. Skip ASCII whitespace within input given position.
|
||||
lexer.ignore_while(Infra::is_ascii_whitespace);
|
||||
|
||||
// 3. If the code point in input pointed to by position is U+003B (;) or U+002C (,), then advance position to the next code point.
|
||||
if (lexer.peek() == ';' || lexer.peek() == ',')
|
||||
lexer.ignore(1);
|
||||
|
||||
// 4. Skip ASCII whitespace within input given position.
|
||||
lexer.ignore_while(Infra::is_ascii_whitespace);
|
||||
}
|
||||
|
||||
// 11. If position is not past the end of input, then:
|
||||
if (!lexer.is_eof()) {
|
||||
// 1. Let urlString be the substring of input from the code point at position to the end of the string.
|
||||
auto url_string = lexer.remaining();
|
||||
|
||||
// 2. If the code point in input pointed to by position is U+0055 (U) or U+0075 (u), then advance position to the next code point. Otherwise, jump to the step labeled skip quotes.
|
||||
if (lexer.peek() == 'U' || lexer.peek() == 'u')
|
||||
lexer.ignore(1);
|
||||
else
|
||||
goto skip_quotes;
|
||||
|
||||
// 3. If the code point in input pointed to by position is U+0052 (R) or U+0072 (r), then advance position to the next code point. Otherwise, jump to the step labeled parse.
|
||||
if (lexer.peek() == 'R' || lexer.peek() == 'r')
|
||||
lexer.ignore(1);
|
||||
else
|
||||
goto parse;
|
||||
|
||||
// 4. If the code point in input pointed to by position is U+004C (L) or U+006C (l), then advance position to the next code point. Otherwise, jump to the step labeled parse.
|
||||
if (lexer.peek() == 'L' || lexer.peek() == 'l')
|
||||
lexer.ignore(1);
|
||||
else
|
||||
goto parse;
|
||||
|
||||
// 5. Skip ASCII whitespace within input given position.
|
||||
lexer.ignore_while(Infra::is_ascii_whitespace);
|
||||
|
||||
// 6. If the code point in input pointed to by position is U+003D (=), then advance position to the next code point. Otherwise, jump to the step labeled parse.
|
||||
if (lexer.peek() == '=')
|
||||
lexer.ignore(1);
|
||||
else
|
||||
goto parse;
|
||||
|
||||
// 7. Skip ASCII whitespace within input given position.
|
||||
lexer.ignore_while(Infra::is_ascii_whitespace);
|
||||
|
||||
skip_quotes : {
|
||||
// 8. Skip quotes: If the code point in input pointed to by position is U+0027 (') or U+0022 ("), then let
|
||||
// quote be that code point, and advance position to the next code point. Otherwise, let quote be the empty
|
||||
// string.
|
||||
Optional<char> quote;
|
||||
if (lexer.peek() == '\'' || lexer.peek() == '"')
|
||||
quote = lexer.consume();
|
||||
|
||||
// 9. Set urlString to the substring of input from the code point at position to the end of the string.
|
||||
// 10. If quote is not the empty string, and there is a code point in urlString equal to quote, then truncate
|
||||
// urlString at that code point, so that it and all subsequent code points are removed.
|
||||
url_string = lexer.consume_while(["e](auto c) {
|
||||
return !quote.has_value() || c != quote.value();
|
||||
});
|
||||
}
|
||||
|
||||
parse:
|
||||
// 11. Parse: Parse urlString relative to document. If that fails, return. Otherwise, set urlRecord to the
|
||||
// resulting URL record.
|
||||
auto maybe_url_record = parse_url(url_string);
|
||||
if (!maybe_url_record.is_valid())
|
||||
return;
|
||||
|
||||
url_record = maybe_url_record;
|
||||
}
|
||||
|
||||
// 12. Set document's will declaratively refresh to true.
|
||||
m_will_declaratively_refresh = true;
|
||||
|
||||
// 13. Perform one or more of the following steps:
|
||||
// - After the refresh has come due (as defined below), if the user has not canceled the redirect and, if meta is
|
||||
// given, document's active sandboxing flag set does not have the sandboxed automatic features browsing context
|
||||
// flag set, then navigate document's node navigable to urlRecord using document, with historyHandling set to
|
||||
// "replace".
|
||||
m_active_refresh_timer = Core::Timer::create_single_shot(time * 1000, [this, has_meta_element = !!meta_element, url_record = move(url_record)]() {
|
||||
if (has_meta_element && active_sandboxing_flag_set().flags & HTML::SandboxingFlagSet::SandboxedAutomaticFeatures)
|
||||
return;
|
||||
|
||||
// FIXME: Use navigables when they're used for all navigation (otherwise, navigable() would be null in some cases)
|
||||
VERIFY(browsing_context());
|
||||
auto request = Fetch::Infrastructure::Request::create(vm());
|
||||
request->set_url(url_record);
|
||||
MUST(browsing_context()->navigate(request, *browsing_context(), false, HTML::HistoryHandlingBehavior::Replace));
|
||||
}).release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// For the purposes of the previous paragraph, a refresh is said to have come due as soon as the later of the
|
||||
// following two conditions occurs:
|
||||
|
||||
// - At least time seconds have elapsed since document's completely loaded time, adjusted to take into
|
||||
// account user or user agent preferences.
|
||||
// m_active_refresh_timer is started in completely_finished_loading after setting the completely loaded time.
|
||||
|
||||
// - If meta is given, at least time seconds have elapsed since meta was inserted into the document document,
|
||||
// adjusted to take into account user or user agent preferences.
|
||||
// NOTE: This is only done if completely loaded time has a value because shared_declarative_refresh_steps is called
|
||||
// by HTMLMetaElement::inserted and if the document hasn't finished loading when the meta element was inserted,
|
||||
// then the document completely finishing loading will _always_ come after inserting the meta element.
|
||||
if (meta_element && m_completely_loaded_time.has_value()) {
|
||||
m_active_refresh_timer->start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue