2020-06-06 08:02:44 -03:00
/*
2022-08-04 16:30:33 -03:00
* Copyright ( c ) 2020 - 2022 , Andreas Kling < kling @ serenityos . org >
2020-06-06 08:02:44 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-06-06 08:02:44 -03:00
*/
2021-01-17 14:17:00 -03:00
# include <AK/Debug.h>
2021-08-10 19:09:35 -03:00
# include <AK/JsonArray.h>
2020-06-06 08:02:44 -03:00
# include <AK/LexicalPath.h>
2021-04-21 19:00:06 -03:00
# include <AK/SourceGenerator.h>
2022-10-29 22:52:07 -03:00
# include <LibWeb/Bindings/MainThreadVM.h>
2020-06-25 18:50:35 -03:00
# include <LibWeb/DOM/Document.h>
2023-04-06 09:50:22 -03:00
# include <LibWeb/DOM/DocumentLoading.h>
2020-06-06 08:02:44 -03:00
# include <LibWeb/DOM/ElementFactory.h>
# include <LibWeb/DOM/Text.h>
2022-08-04 16:30:33 -03:00
# include <LibWeb/HTML/NavigationParams.h>
2021-09-25 18:15:48 -03:00
# include <LibWeb/HTML/Parser/HTMLParser.h>
2020-06-06 08:02:44 -03:00
# include <LibWeb/Loader/FrameLoader.h>
# include <LibWeb/Loader/ResourceLoader.h>
2023-03-29 19:46:18 -03:00
# include <LibWeb/Namespace.h>
2020-07-28 14:27:41 -03:00
# include <LibWeb/Page/Page.h>
2022-09-16 10:01:47 -03:00
# include <LibWeb/Platform/ImageCodecPlugin.h>
2022-03-28 08:55:17 -03:00
# include <LibWeb/XML/XMLDocumentBuilder.h>
2020-06-06 08:02:44 -03:00
namespace Web {
2022-12-04 15:02:33 -03:00
static DeprecatedString s_default_favicon_path = " /res/icons/16x16/app-browser.png " ;
2021-08-06 17:28:22 -03:00
static RefPtr < Gfx : : Bitmap > s_default_favicon_bitmap ;
2022-12-04 15:02:33 -03:00
void FrameLoader : : set_default_favicon_path ( DeprecatedString path )
2022-04-03 11:36:28 -03:00
{
s_default_favicon_path = move ( path ) ;
}
2021-11-18 11:01:28 -03:00
FrameLoader : : FrameLoader ( HTML : : BrowsingContext & browsing_context )
2021-05-30 07:36:53 -03:00
: m_browsing_context ( browsing_context )
2020-06-06 08:02:44 -03:00
{
2021-08-06 17:28:22 -03:00
if ( ! s_default_favicon_bitmap ) {
2023-01-20 16:06:05 -03:00
s_default_favicon_bitmap = Gfx : : Bitmap : : load_from_file ( s_default_favicon_path ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-08-06 17:28:22 -03:00
VERIFY ( s_default_favicon_bitmap ) ;
}
2020-06-06 08:02:44 -03:00
}
2022-03-14 16:21:51 -03:00
FrameLoader : : ~ FrameLoader ( ) = default ;
2020-06-06 08:02:44 -03:00
2021-09-12 01:15:15 -03:00
bool FrameLoader : : load ( LoadRequest & request , Type type )
2020-06-06 08:02:44 -03:00
{
2020-09-28 06:55:26 -03:00
if ( ! request . is_valid ( ) ) {
load_error_page ( request . url ( ) , " Invalid request " ) ;
2020-06-06 08:02:44 -03:00
return false ;
}
2023-02-26 20:09:02 -03:00
if ( ! m_browsing_context - > is_frame_nesting_allowed ( request . url ( ) ) ) {
2021-04-19 09:30:08 -03:00
dbgln ( " No further recursion is allowed for the frame, abort load! " ) ;
return false ;
}
2023-03-15 08:28:33 -03:00
request . set_main_resource ( true ) ;
2020-09-28 06:55:26 -03:00
auto & url = request . url ( ) ;
2022-11-09 20:29:35 -03:00
if ( type = = Type : : Navigation | | type = = Type : : Reload | | type = = Type : : Redirect ) {
2022-09-25 07:28:40 -03:00
if ( auto * page = browsing_context ( ) . page ( ) ) {
2023-02-26 20:09:02 -03:00
if ( & page - > top_level_browsing_context ( ) = = m_browsing_context )
2022-11-09 20:29:35 -03:00
page - > client ( ) . page_did_start_loading ( url , type = = Type : : Redirect ) ;
2022-09-25 07:28:40 -03:00
}
2020-11-12 14:23:05 -03:00
}
2020-06-06 08:02:44 -03:00
2022-02-17 20:23:01 -03:00
// https://fetch.spec.whatwg.org/#concept-fetch
// Step 12: If request’ s header list does not contain `Accept`, then:
// 1. Let value be `*/*`. (NOTE: Not necessary as we're about to override it)
// 2. A user agent should set value to the first matching statement, if any, switching on request’ s destination:
// -> "document"
// -> "frame"
// -> "iframe"
// `text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`
if ( ! request . headers ( ) . contains ( " Accept " ) )
request . set_header ( " Accept " , " text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 " ) ;
2021-05-28 21:17:36 -03:00
set_resource ( ResourceLoader : : the ( ) . load_resource ( Resource : : Type : : Generic , request ) ) ;
2020-11-07 06:03:52 -03:00
if ( type = = Type : : IFrame )
return true ;
2022-09-28 20:30:58 -03:00
if ( url . scheme ( ) = = " http " | | url . scheme ( ) = = " https " ) {
2021-09-12 18:33:23 -03:00
AK : : URL favicon_url ;
2022-09-28 20:30:58 -03:00
favicon_url . set_scheme ( url . scheme ( ) ) ;
2020-06-06 08:02:44 -03:00
favicon_url . set_host ( url . host ( ) ) ;
2021-09-13 17:12:16 -03:00
favicon_url . set_port ( url . port_or_default ( ) ) ;
2021-05-27 16:27:51 -03:00
favicon_url . set_paths ( { " favicon.ico " } ) ;
2020-06-06 08:02:44 -03:00
ResourceLoader : : the ( ) . load (
favicon_url ,
2021-04-03 10:11:36 -03:00
[ this , favicon_url ] ( auto data , auto & , auto ) {
2022-04-03 14:56:15 -03:00
// Always fetch the current document
auto * document = this - > browsing_context ( ) . active_document ( ) ;
if ( document & & document - > has_active_favicon ( ) )
return ;
2021-08-07 05:37:26 -03:00
dbgln_if ( SPAM_DEBUG , " Favicon downloaded, {} bytes from {} " , data . size ( ) , favicon_url ) ;
2021-09-24 11:58:03 -03:00
if ( data . is_empty ( ) )
return ;
2021-08-06 17:28:22 -03:00
RefPtr < Gfx : : Bitmap > favicon_bitmap ;
2022-09-16 10:01:47 -03:00
auto decoded_image = Platform : : ImageCodecPlugin : : the ( ) . decode_image ( data ) ;
2021-11-20 07:25:46 -03:00
if ( ! decoded_image . has_value ( ) | | decoded_image - > frames . is_empty ( ) ) {
dbgln ( " Could not decode favicon {} " , favicon_url ) ;
2021-08-06 17:28:22 -03:00
} else {
2021-11-20 07:25:46 -03:00
favicon_bitmap = decoded_image - > frames [ 0 ] . bitmap ;
dbgln_if ( IMAGE_DECODER_DEBUG , " Decoded favicon, {} " , favicon_bitmap - > size ( ) ) ;
2021-07-26 20:12:53 -03:00
}
2021-08-06 17:28:22 -03:00
load_favicon ( favicon_bitmap ) ;
} ,
[ this ] ( auto & , auto ) {
2022-04-03 14:56:15 -03:00
// Always fetch the current document
auto * document = this - > browsing_context ( ) . active_document ( ) ;
if ( document & & document - > has_active_favicon ( ) )
return ;
2021-08-06 17:28:22 -03:00
load_favicon ( ) ;
2020-06-06 08:02:44 -03:00
} ) ;
2021-08-06 17:28:22 -03:00
} else {
load_favicon ( ) ;
2020-06-06 08:02:44 -03:00
}
return true ;
}
2021-09-12 18:33:23 -03:00
bool FrameLoader : : load ( const AK : : URL & url , Type type )
2020-09-28 06:55:26 -03:00
{
2021-08-07 05:37:26 -03:00
dbgln_if ( SPAM_DEBUG , " FrameLoader::load: {} " , url ) ;
2020-09-28 06:55:26 -03:00
if ( ! url . is_valid ( ) ) {
load_error_page ( url , " Invalid URL " ) ;
return false ;
}
2021-05-30 07:36:53 -03:00
auto request = LoadRequest : : create_for_url_on_page ( url , browsing_context ( ) . page ( ) ) ;
2020-09-28 06:55:26 -03:00
return load ( request , type ) ;
}
2021-11-10 20:55:02 -03:00
void FrameLoader : : load_html ( StringView html , const AK : : URL & url )
2020-10-08 17:03:16 -03:00
{
2022-10-29 22:52:07 -03:00
auto & vm = Bindings : : main_thread_vm ( ) ;
auto response = Fetch : : Infrastructure : : Response : : create ( vm ) ;
2022-08-04 16:30:33 -03:00
response - > url_list ( ) . append ( url ) ;
HTML : : NavigationParams navigation_params {
. id = { } ,
. request = nullptr ,
2022-10-29 22:52:07 -03:00
. response = response ,
2022-08-04 16:30:33 -03:00
. origin = HTML : : Origin { } ,
. policy_container = HTML : : PolicyContainer { } ,
. final_sandboxing_flag_set = HTML : : SandboxingFlagSet { } ,
. cross_origin_opener_policy = HTML : : CrossOriginOpenerPolicy { } ,
. coop_enforcement_result = HTML : : CrossOriginOpenerPolicyEnforcementResult { } ,
. reserved_environment = { } ,
. browsing_context = browsing_context ( ) ,
2023-04-05 03:20:05 -03:00
. navigable = nullptr ,
2022-08-04 16:30:33 -03:00
} ;
2023-02-14 17:49:02 -03:00
auto document = DOM : : Document : : create_and_initialize ( DOM : : Document : : Type : : HTML , " text/html " , move ( navigation_params ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2022-10-02 13:55:31 -03:00
browsing_context ( ) . set_active_document ( document ) ;
2022-08-04 16:30:33 -03:00
2022-02-21 17:54:21 -03:00
auto parser = HTML : : HTMLParser : : create ( document , html , " utf-8 " ) ;
parser - > run ( url ) ;
2020-10-08 17:03:16 -03:00
}
2022-12-04 15:02:33 -03:00
static DeprecatedString s_error_page_url = " file:///res/html/error.html " ;
2022-04-03 13:01:10 -03:00
2022-12-04 15:02:33 -03:00
void FrameLoader : : set_error_page_url ( DeprecatedString error_page_url )
2022-04-03 13:01:10 -03:00
{
s_error_page_url = error_page_url ;
}
2021-01-11 11:01:33 -03:00
// FIXME: Use an actual templating engine (our own one when it's built, preferably
// with a way to check these usages at compile time)
2022-12-04 15:02:33 -03:00
void FrameLoader : : load_error_page ( const AK : : URL & failed_url , DeprecatedString const & error )
2020-06-06 08:02:44 -03:00
{
2023-01-18 11:44:37 -03:00
LoadRequest request = LoadRequest : : create_for_url_on_page ( s_error_page_url , browsing_context ( ) . page ( ) ) ;
2020-06-06 08:02:44 -03:00
ResourceLoader : : the ( ) . load (
2023-01-18 11:44:37 -03:00
request ,
2021-04-03 10:11:36 -03:00
[ this , failed_url , error ] ( auto data , auto & , auto ) {
2021-02-23 16:42:32 -03:00
VERIFY ( ! data . is_null ( ) ) ;
2021-04-21 19:00:06 -03:00
StringBuilder builder ;
SourceGenerator generator { builder } ;
2022-12-05 22:12:49 -03:00
generator . set ( " failed_url " , escape_html_entities ( failed_url . to_deprecated_string ( ) ) ) ;
2021-04-22 05:17:00 -03:00
generator . set ( " error " , escape_html_entities ( error ) ) ;
2021-04-21 19:00:06 -03:00
generator . append ( data ) ;
2023-01-18 11:44:25 -03:00
load_html ( generator . as_string_view ( ) , s_error_page_url ) ;
2020-06-06 08:02:44 -03:00
} ,
2021-04-03 10:11:36 -03:00
[ ] ( auto & error , auto ) {
2021-01-17 14:17:00 -03:00
dbgln ( " Failed to load error page: {} " , error ) ;
2021-02-23 16:42:32 -03:00
VERIFY_NOT_REACHED ( ) ;
2020-06-06 08:02:44 -03:00
} ) ;
}
2021-08-06 17:28:22 -03:00
void FrameLoader : : load_favicon ( RefPtr < Gfx : : Bitmap > bitmap )
{
if ( auto * page = browsing_context ( ) . page ( ) ) {
if ( bitmap )
page - > client ( ) . page_did_change_favicon ( * bitmap ) ;
2022-04-03 11:36:28 -03:00
else if ( s_default_favicon_bitmap )
2021-08-06 17:28:22 -03:00
page - > client ( ) . page_did_change_favicon ( * s_default_favicon_bitmap ) ;
}
}
2020-06-06 08:38:08 -03:00
void FrameLoader : : resource_did_load ( )
{
2023-02-28 17:42:51 -03:00
// This prevents us setting up the document of a removed browsing context container (BCC, e.g. <iframe>), which will cause a crash
// if the document contains a script that inserts another BCC as this will use the stale browsing context it previously set up,
// even if it's reinserted.
// Example:
// index.html:
// ```
// <body><script>
// var i = document.createElement("iframe");
// i.src = "b.html";
// document.body.append(i);
// i.remove();
// </script>
// ```
// b.html:
// ```
// <body><script>
// var i = document.createElement("iframe");
// document.body.append(i);
// </script>
// ```
// Required by Prebid.js, which does this by inserting an <iframe> into a <div> in the active document via innerHTML,
// then transfers it to the <html> element:
// https://github.com/prebid/Prebid.js/blob/7b7389c5abdd05626f71c3df606a93713d1b9f85/src/utils.js#L597
// This is done in the spec by removing all tasks and aborting all fetches when a document is destroyed:
// https://html.spec.whatwg.org/multipage/document-lifecycle.html#destroy-a-document
if ( browsing_context ( ) . has_been_discarded ( ) )
return ;
2020-06-06 08:38:08 -03:00
auto url = resource ( ) - > url ( ) ;
2022-02-12 10:42:48 -03:00
// For 3xx (Redirection) responses, the Location value refers to the preferred target resource for automatically redirecting the request.
auto status_code = resource ( ) - > status_code ( ) ;
if ( status_code . has_value ( ) & & * status_code > = 300 & & * status_code < = 399 ) {
auto location = resource ( ) - > response_headers ( ) . get ( " Location " ) ;
if ( location . has_value ( ) ) {
if ( m_redirects_count > maximum_redirects_allowed ) {
m_redirects_count = 0 ;
load_error_page ( url , " Too many redirects " ) ;
return ;
}
m_redirects_count + + ;
2022-11-09 20:29:35 -03:00
load ( url . complete_url ( location . value ( ) ) , Type : : Redirect ) ;
2021-05-11 16:22:56 -03:00
return ;
}
2020-06-06 08:38:08 -03:00
}
2021-05-11 16:22:56 -03:00
m_redirects_count = 0 ;
2020-06-06 08:38:08 -03:00
2021-05-12 05:21:00 -03:00
if ( resource ( ) - > has_encoding ( ) ) {
2021-08-07 05:37:26 -03:00
dbgln_if ( RESOURCE_DEBUG , " This content has MIME type '{}', encoding '{}' " , resource ( ) - > mime_type ( ) , resource ( ) - > encoding ( ) . value ( ) ) ;
2021-05-12 05:21:00 -03:00
} else {
2021-08-07 05:37:26 -03:00
dbgln_if ( RESOURCE_DEBUG , " This content has MIME type '{}', encoding unknown " , resource ( ) - > mime_type ( ) ) ;
2021-05-12 05:21:00 -03:00
}
2020-12-13 13:38:03 -03:00
2022-08-25 15:04:27 -03:00
auto final_sandboxing_flag_set = HTML : : SandboxingFlagSet { } ;
// (Part of https://html.spec.whatwg.org/#navigating-across-documents)
// 3. Let responseOrigin be the result of determining the origin given browsingContext, resource's url, finalSandboxFlags, and incumbentNavigationOrigin.
// FIXME: Pass incumbentNavigationOrigin
auto response_origin = HTML : : determine_the_origin ( browsing_context ( ) , url , final_sandboxing_flag_set , { } ) ;
2022-10-29 22:52:07 -03:00
auto & vm = Bindings : : main_thread_vm ( ) ;
auto response = Fetch : : Infrastructure : : Response : : create ( vm ) ;
2022-08-04 16:30:33 -03:00
response - > url_list ( ) . append ( url ) ;
HTML : : NavigationParams navigation_params {
. id = { } ,
. request = nullptr ,
2022-10-29 22:52:07 -03:00
. response = response ,
2022-08-25 15:04:27 -03:00
. origin = move ( response_origin ) ,
2022-08-04 16:30:33 -03:00
. policy_container = HTML : : PolicyContainer { } ,
2022-10-29 22:52:07 -03:00
. final_sandboxing_flag_set = final_sandboxing_flag_set ,
2022-08-04 16:30:33 -03:00
. cross_origin_opener_policy = HTML : : CrossOriginOpenerPolicy { } ,
. coop_enforcement_result = HTML : : CrossOriginOpenerPolicyEnforcementResult { } ,
. reserved_environment = { } ,
. browsing_context = browsing_context ( ) ,
2023-04-05 03:20:05 -03:00
. navigable = nullptr ,
2022-08-04 16:30:33 -03:00
} ;
2023-02-14 17:49:02 -03:00
auto document = DOM : : Document : : create_and_initialize ( DOM : : Document : : Type : : HTML , " text/html " , move ( navigation_params ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2020-12-13 13:38:03 -03:00
document - > set_url ( url ) ;
2021-05-12 05:32:41 -03:00
document - > set_encoding ( resource ( ) - > encoding ( ) ) ;
2020-12-13 13:38:03 -03:00
document - > set_content_type ( resource ( ) - > mime_type ( ) ) ;
2020-06-06 09:06:37 -03:00
2021-09-09 08:14:32 -03:00
browsing_context ( ) . set_active_document ( document ) ;
2022-09-21 12:12:00 -03:00
if ( auto * page = browsing_context ( ) . page ( ) )
page - > client ( ) . page_did_create_main_document ( ) ;
2020-12-13 13:38:03 -03:00
if ( ! parse_document ( * document , resource ( ) - > encoded_data ( ) ) ) {
2020-06-06 09:06:37 -03:00
load_error_page ( url , " Failed to parse content. " ) ;
return ;
}
2020-06-06 08:38:08 -03:00
if ( ! url . fragment ( ) . is_empty ( ) )
2021-05-30 07:36:53 -03:00
browsing_context ( ) . scroll_to_anchor ( url . fragment ( ) ) ;
2021-09-08 06:56:50 -03:00
else
2022-04-06 10:32:38 -03:00
browsing_context ( ) . scroll_to ( { 0 , 0 } ) ;
2020-09-22 12:53:40 -03:00
2021-05-30 07:36:53 -03:00
if ( auto * page = browsing_context ( ) . page ( ) )
2020-12-08 17:44:42 -03:00
page - > client ( ) . page_did_finish_loading ( url ) ;
2020-06-06 08:38:08 -03:00
}
void FrameLoader : : resource_did_fail ( )
{
2023-02-28 17:42:51 -03:00
// See comment in resource_did_load() about why this is done.
if ( browsing_context ( ) . has_been_discarded ( ) )
return ;
2020-06-06 08:38:08 -03:00
load_error_page ( resource ( ) - > url ( ) , resource ( ) - > error ( ) ) ;
}
2020-06-06 08:02:44 -03:00
}