LibWeb: Start building a new Resource class to share more resources

A Resource represents a resource that we're loading, have loaded or
will soon load. Basically, it's a downloadable resource that can be
shared by multiple clients.

A typical usecase is multiple <img> elements with the same src.
In a future patch, we will try to make sure that those <img> elements
get the same Resource if possible. This will reduce network usage,
memory usage, and CPU usage. :^)

For now, this first patch simply introduces the mechanism.

You get a Resource by calling ResourceLoader::load_resource().
To get notified about changes to a Resource's load status, you inherit
from ResourceClient and implement the callbacks you're interested in.

This patch turns HTMLImageElement into a ResourceClient.
This commit is contained in:
Andreas Kling 2020-06-01 21:33:23 +02:00
parent 7be9cf8d36
commit 5ed66cb8d9
Notes: sideshowbarker 2024-07-19 05:54:21 +09:00
8 changed files with 274 additions and 23 deletions

View file

@ -83,6 +83,7 @@ set(SOURCES
Layout/LayoutWidget.cpp
Layout/LineBox.cpp
Layout/LineBoxFragment.cpp
Loader/Resource.cpp
Loader/ResourceLoader.cpp
PageView.cpp
Parser/CSSParser.cpp

View file

@ -55,30 +55,40 @@ void HTMLImageElement::parse_attribute(const FlyString& name, const String& valu
void HTMLImageElement::load_image(const String& src)
{
URL src_url = document().complete_url(src);
ResourceLoader::the().load(src_url, [this, weak_element = make_weak_ptr()](auto data, auto&) {
if (!weak_element) {
dbg() << "HTMLImageElement: Load completed after element destroyed.";
return;
}
if (data.is_null()) {
dbg() << "HTMLImageElement: Failed to load " << this->src();
return;
}
auto resource = ResourceLoader::the().load_resource(src_url);
set_resource(resource);
}
m_encoded_data = data;
m_image_decoder = Gfx::ImageDecoder::create(m_encoded_data.data(), m_encoded_data.size());
void HTMLImageElement::resource_did_load()
{
ASSERT(resource());
if (m_image_decoder->is_animated() && m_image_decoder->frame_count() > 1) {
const auto& first_frame = m_image_decoder->frame(0);
m_timer->set_interval(first_frame.duration);
m_timer->on_timeout = [this] { animate(); };
m_timer->start();
}
if (!resource()->has_encoded_data()) {
dbg() << "HTMLImageElement: Resource did load, but encoded data empty: " << this->src();
return;
}
document().update_layout();
dbg() << "HTMLImageElement: Resource did load, encoded data looks tasty: " << this->src();
dispatch_event(Event::create("load"));
});
m_image_decoder = Gfx::ImageDecoder::create(resource()->encoded_data());
if (m_image_decoder->is_animated() && m_image_decoder->frame_count() > 1) {
const auto& first_frame = m_image_decoder->frame(0);
m_timer->set_interval(first_frame.duration);
m_timer->on_timeout = [this] { animate(); };
m_timer->start();
}
document().update_layout();
dispatch_event(Event::create("load"));
}
void HTMLImageElement::resource_did_fail()
{
dbg() << "HTMLImageElement: Resource did fail: " << this->src();
m_image_decoder = nullptr;
document().update_layout();
dispatch_event(Event::create("error"));
}
void HTMLImageElement::animate()
@ -162,7 +172,8 @@ void HTMLImageElement::set_volatile(Badge<LayoutDocument>, bool v)
bool has_image = m_image_decoder->set_nonvolatile();
if (has_image)
return;
m_image_decoder = Gfx::ImageDecoder::create(m_encoded_data.data(), m_encoded_data.size());
ASSERT(resource());
m_image_decoder = Gfx::ImageDecoder::create(resource()->encoded_data());
}
}

View file

@ -30,12 +30,15 @@
#include <LibCore/Forward.h>
#include <LibGfx/Forward.h>
#include <LibWeb/DOM/HTMLElement.h>
#include <LibWeb/Loader/Resource.h>
namespace Web {
class LayoutDocument;
class HTMLImageElement : public HTMLElement {
class HTMLImageElement final
: public HTMLElement
, public ResourceClient {
public:
using WrapperType = Bindings::HTMLImageElementWrapper;
@ -55,6 +58,9 @@ public:
void set_volatile(Badge<LayoutDocument>, bool);
private:
virtual void resource_did_load() override;
virtual void resource_did_fail() override;
void load_image(const String& src);
void animate();
@ -62,7 +68,6 @@ private:
virtual RefPtr<LayoutNode> create_layout_node(const StyleProperties* parent_style) const override;
RefPtr<Gfx::ImageDecoder> m_image_decoder;
ByteBuffer m_encoded_data;
size_t m_current_frame_index { 0 };
size_t m_loops_completed { 0 };

View file

@ -51,6 +51,8 @@ class LayoutNode;
class MouseEvent;
class Node;
class Origin;
class Resource;
class ResourceLoader;
class Selector;
class StyleResolver;
class StyleRule;

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWeb/DOM/HTMLImageElement.h>
#include <LibWeb/Loader/Resource.h>
namespace Web {
NonnullRefPtr<Resource> Resource::create(Badge<ResourceLoader>, const URL& url)
{
return adopt(*new Resource(url));
}
Resource::Resource(const URL& url)
: m_url(url)
{
}
Resource::~Resource()
{
}
void Resource::did_load(Badge<ResourceLoader>, const ByteBuffer& data, const HashMap<String, String, CaseInsensitiveStringTraits>& headers)
{
ASSERT(!m_loaded);
m_encoded_data = data;
m_response_headers = headers;
m_loaded = true;
for_each_client([](auto& client) {
client.resource_did_load();
});
}
void Resource::did_fail(Badge<ResourceLoader>, const String& error)
{
m_error = error;
m_failed = true;
for_each_client([](auto& client) {
client.resource_did_fail();
});
}
void Resource::register_client(Badge<ResourceClient>, ResourceClient& client)
{
ASSERT(!m_clients.contains(&client));
m_clients.set(&client);
}
void Resource::unregister_client(Badge<ResourceClient>, ResourceClient& client)
{
ASSERT(m_clients.contains(&client));
m_clients.remove(&client);
}
void ResourceClient::set_resource(Resource* resource)
{
if (m_resource)
m_resource->unregister_client({}, *this);
m_resource = resource;
if (m_resource) {
m_resource->register_client({}, *this);
// Make sure that reused resources also have their load callback fired.
if (resource->is_loaded())
resource_did_load();
// Make sure that reused resources also have their fail callback fired.
if (resource->is_failed())
resource_did_fail();
}
}
ResourceClient::~ResourceClient()
{
if (m_resource)
m_resource->unregister_client({}, *this);
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h>
#include <AK/HashTable.h>
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <AK/URL.h>
#include <LibWeb/Forward.h>
namespace Web {
class ResourceClient;
class Resource : public RefCounted<Resource> {
AK_MAKE_NONCOPYABLE(Resource);
AK_MAKE_NONMOVABLE(Resource);
public:
static NonnullRefPtr<Resource> create(Badge<ResourceLoader>, const URL&);
~Resource();
bool is_loaded() const { return m_loaded; }
bool is_failed() const { return m_failed; }
const String& error() const { return m_error; }
bool has_encoded_data() const { return !m_encoded_data.is_null(); }
const URL& url() const { return m_url; }
const ByteBuffer& encoded_data() const { return m_encoded_data; }
void register_client(Badge<ResourceClient>, ResourceClient&);
void unregister_client(Badge<ResourceClient>, ResourceClient&);
template<typename Callback>
void for_each_client(Callback callback)
{
// FIXME: This should use some kind of smart pointer to ResourceClient!
Vector<ResourceClient*, 16> clients_copy;
clients_copy.ensure_capacity(m_clients.size());
for (auto* client : m_clients)
clients_copy.append(client);
for (auto* client : clients_copy)
callback(*client);
}
void did_load(Badge<ResourceLoader>, const ByteBuffer& data, const HashMap<String, String, CaseInsensitiveStringTraits>& headers);
void did_fail(Badge<ResourceLoader>, const String& error);
private:
explicit Resource(const URL&);
URL m_url;
ByteBuffer m_encoded_data;
bool m_loaded { false };
bool m_failed { false };
String m_error;
HashMap<String, String, CaseInsensitiveStringTraits> m_response_headers;
HashTable<ResourceClient*> m_clients;
};
class ResourceClient {
public:
virtual ~ResourceClient();
virtual void resource_did_load() { }
virtual void resource_did_fail() { }
protected:
Resource* resource() { return m_resource; }
const Resource* resource() const { return m_resource; }
void set_resource(Resource*);
private:
RefPtr<Resource> m_resource;
};
}

View file

@ -31,6 +31,7 @@
#include <LibCore/File.h>
#include <LibProtocol/Client.h>
#include <LibProtocol/Download.h>
#include <LibWeb/Loader/Resource.h>
#include <LibWeb/Loader/ResourceLoader.h>
namespace Web {
@ -68,6 +69,25 @@ void ResourceLoader::load_sync(const URL& url, Function<void(const ByteBuffer&,
loop.exec();
}
RefPtr<Resource> ResourceLoader::load_resource(const URL& url)
{
if (!url.is_valid())
return nullptr;
auto resource = Resource::create({}, url);
load(
url,
[=](auto& data, auto& headers) {
const_cast<Resource&>(*resource).did_load({}, data, headers);
},
[=](auto& error) {
const_cast<Resource&>(*resource).did_fail({}, error);
});
return resource;
}
void ResourceLoader::load(const URL& url, Function<void(const ByteBuffer&, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback)
{
if (is_port_blocked(url.port())) {

View file

@ -29,6 +29,7 @@
#include <AK/Function.h>
#include <AK/URL.h>
#include <LibCore/Object.h>
#include <LibWeb/Forward.h>
namespace Protocol {
class Client;
@ -41,6 +42,8 @@ class ResourceLoader : public Core::Object {
public:
static ResourceLoader& the();
RefPtr<Resource> load_resource(const URL&);
void load(const URL&, Function<void(const ByteBuffer&, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback = nullptr);
void load_sync(const URL&, Function<void(const ByteBuffer&, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers)> success_callback, Function<void(const String&)> error_callback = nullptr);