mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-12 04:22:28 +00:00
Everywhere: Hoist the Libraries folder to the top-level
This commit is contained in:
parent
950e819ee7
commit
93712b24bf
Notes:
github-actions[bot]
2024-11-10 11:51:52 +00:00
Author: https://github.com/trflynn89
Commit: 93712b24bf
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2256
Reviewed-by: https://github.com/sideshowbarker
4547 changed files with 104 additions and 113 deletions
25
Libraries/LibWeb/HTML/AbstractWorker.cpp
Normal file
25
Libraries/LibWeb/HTML/AbstractWorker.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/HTML/AbstractWorker.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror
|
||||
WebIDL::CallbackType* AbstractWorker::onerror()
|
||||
{
|
||||
return this_event_target().event_handler_attribute(HTML::EventNames::error);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror
|
||||
void AbstractWorker::set_onerror(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
this_event_target().set_event_handler_attribute(HTML::EventNames::error, event_handler);
|
||||
}
|
||||
|
||||
}
|
25
Libraries/LibWeb/HTML/AbstractWorker.h
Normal file
25
Libraries/LibWeb/HTML/AbstractWorker.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/workers.html#abstractworker
|
||||
class AbstractWorker {
|
||||
public:
|
||||
virtual ~AbstractWorker() = default;
|
||||
|
||||
WebIDL::CallbackType* onerror();
|
||||
void set_onerror(WebIDL::CallbackType*);
|
||||
|
||||
protected:
|
||||
virtual DOM::EventTarget& this_event_target() = 0;
|
||||
};
|
||||
|
||||
}
|
4
Libraries/LibWeb/HTML/AbstractWorker.idl
Normal file
4
Libraries/LibWeb/HTML/AbstractWorker.idl
Normal file
|
@ -0,0 +1,4 @@
|
|||
// https://html.spec.whatwg.org/multipage/workers.html#abstractworker
|
||||
interface mixin AbstractWorker {
|
||||
attribute EventHandler onerror;
|
||||
};
|
16
Libraries/LibWeb/HTML/ActivateTab.h
Normal file
16
Libraries/LibWeb/HTML/ActivateTab.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
enum class ActivateTab {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
}
|
59
Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.cpp
Normal file
59
Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibJS/Heap/Heap.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(AnimatedBitmapDecodedImageData);
|
||||
|
||||
ErrorOr<JS::NonnullGCPtr<AnimatedBitmapDecodedImageData>> AnimatedBitmapDecodedImageData::create(JS::Realm& realm, Vector<Frame>&& frames, size_t loop_count, bool animated)
|
||||
{
|
||||
return realm.heap().allocate<AnimatedBitmapDecodedImageData>(realm, move(frames), loop_count, animated);
|
||||
}
|
||||
|
||||
AnimatedBitmapDecodedImageData::AnimatedBitmapDecodedImageData(Vector<Frame>&& frames, size_t loop_count, bool animated)
|
||||
: m_frames(move(frames))
|
||||
, m_loop_count(loop_count)
|
||||
, m_animated(animated)
|
||||
{
|
||||
}
|
||||
|
||||
AnimatedBitmapDecodedImageData::~AnimatedBitmapDecodedImageData() = default;
|
||||
|
||||
RefPtr<Gfx::ImmutableBitmap> AnimatedBitmapDecodedImageData::bitmap(size_t frame_index, Gfx::IntSize) const
|
||||
{
|
||||
if (frame_index >= m_frames.size())
|
||||
return nullptr;
|
||||
return m_frames[frame_index].bitmap;
|
||||
}
|
||||
|
||||
int AnimatedBitmapDecodedImageData::frame_duration(size_t frame_index) const
|
||||
{
|
||||
if (frame_index >= m_frames.size())
|
||||
return 0;
|
||||
return m_frames[frame_index].duration;
|
||||
}
|
||||
|
||||
Optional<CSSPixels> AnimatedBitmapDecodedImageData::intrinsic_width() const
|
||||
{
|
||||
return m_frames.first().bitmap->width();
|
||||
}
|
||||
|
||||
Optional<CSSPixels> AnimatedBitmapDecodedImageData::intrinsic_height() const
|
||||
{
|
||||
return m_frames.first().bitmap->height();
|
||||
}
|
||||
|
||||
Optional<CSSPixelFraction> AnimatedBitmapDecodedImageData::intrinsic_aspect_ratio() const
|
||||
{
|
||||
return CSSPixels(m_frames.first().bitmap->width()) / CSSPixels(m_frames.first().bitmap->height());
|
||||
}
|
||||
|
||||
}
|
46
Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.h
Normal file
46
Libraries/LibWeb/HTML/AnimatedBitmapDecodedImageData.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibWeb/HTML/DecodedImageData.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class AnimatedBitmapDecodedImageData final : public DecodedImageData {
|
||||
JS_CELL(AnimatedBitmapDecodedImageData, DecodedImageData);
|
||||
JS_DECLARE_ALLOCATOR(AnimatedBitmapDecodedImageData);
|
||||
|
||||
public:
|
||||
struct Frame {
|
||||
RefPtr<Gfx::ImmutableBitmap> bitmap;
|
||||
int duration { 0 };
|
||||
};
|
||||
|
||||
static ErrorOr<JS::NonnullGCPtr<AnimatedBitmapDecodedImageData>> create(JS::Realm&, Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
virtual ~AnimatedBitmapDecodedImageData() override;
|
||||
|
||||
virtual RefPtr<Gfx::ImmutableBitmap> bitmap(size_t frame_index, Gfx::IntSize = {}) const override;
|
||||
virtual int frame_duration(size_t frame_index) const override;
|
||||
|
||||
virtual size_t frame_count() const override { return m_frames.size(); }
|
||||
virtual size_t loop_count() const override { return m_loop_count; }
|
||||
virtual bool is_animated() const override { return m_animated; }
|
||||
|
||||
virtual Optional<CSSPixels> intrinsic_width() const override;
|
||||
virtual Optional<CSSPixels> intrinsic_height() const override;
|
||||
virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const override;
|
||||
|
||||
private:
|
||||
AnimatedBitmapDecodedImageData(Vector<Frame>&&, size_t loop_count, bool animated);
|
||||
|
||||
Vector<Frame> m_frames;
|
||||
size_t m_loop_count { 0 };
|
||||
bool m_animated { false };
|
||||
};
|
||||
|
||||
}
|
49
Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.cpp
Normal file
49
Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <LibWeb/HTML/AnimationFrameCallbackDriver.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(AnimationFrameCallbackDriver);
|
||||
|
||||
void AnimationFrameCallbackDriver::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_callbacks);
|
||||
visitor.visit(m_executing_callbacks);
|
||||
}
|
||||
|
||||
WebIDL::UnsignedLong AnimationFrameCallbackDriver::add(Callback handler)
|
||||
{
|
||||
auto id = ++m_animation_frame_callback_identifier;
|
||||
m_callbacks.set(id, handler);
|
||||
return id;
|
||||
}
|
||||
|
||||
bool AnimationFrameCallbackDriver::remove(WebIDL::UnsignedLong id)
|
||||
{
|
||||
return m_callbacks.remove(id);
|
||||
}
|
||||
|
||||
bool AnimationFrameCallbackDriver::has_callbacks() const
|
||||
{
|
||||
return !m_callbacks.is_empty();
|
||||
}
|
||||
|
||||
void AnimationFrameCallbackDriver::run(double now)
|
||||
{
|
||||
AK::ScopeGuard guard { [&]() { m_executing_callbacks.clear(); } };
|
||||
m_executing_callbacks = move(m_callbacks);
|
||||
|
||||
for (auto& [id, callback] : m_executing_callbacks)
|
||||
callback->function()(now);
|
||||
}
|
||||
|
||||
}
|
40
Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h
Normal file
40
Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibJS/Heap/GCPtr.h>
|
||||
#include <LibJS/Heap/HeapFunction.h>
|
||||
#include <LibWeb/WebIDL/Types.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class AnimationFrameCallbackDriver final : public JS::Cell {
|
||||
JS_CELL(AnimationFrameCallbackDriver, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(AnimationFrameCallbackDriver);
|
||||
|
||||
using Callback = JS::NonnullGCPtr<JS::HeapFunction<void(double)>>;
|
||||
|
||||
public:
|
||||
[[nodiscard]] WebIDL::UnsignedLong add(Callback handler);
|
||||
bool remove(WebIDL::UnsignedLong);
|
||||
bool has_callbacks() const;
|
||||
void run(double now);
|
||||
|
||||
private:
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frame-callback-identifier
|
||||
WebIDL::UnsignedLong m_animation_frame_callback_identifier { 0 };
|
||||
|
||||
OrderedHashMap<WebIDL::UnsignedLong, Callback> m_callbacks;
|
||||
OrderedHashMap<WebIDL::UnsignedLong, Callback> m_executing_callbacks;
|
||||
};
|
||||
|
||||
}
|
9
Libraries/LibWeb/HTML/AnimationFrameProvider.idl
Normal file
9
Libraries/LibWeb/HTML/AnimationFrameProvider.idl
Normal file
|
@ -0,0 +1,9 @@
|
|||
#import <HighResolutionTime/DOMHighResTimeStamp.idl>
|
||||
|
||||
callback FrameRequestCallback = undefined (DOMHighResTimeStamp time);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animationframeprovider
|
||||
interface mixin AnimationFrameProvider {
|
||||
unsigned long requestAnimationFrame(FrameRequestCallback callback);
|
||||
undefined cancelAnimationFrame(unsigned long handle);
|
||||
};
|
88
Libraries/LibWeb/HTML/AttributeNames.cpp
Normal file
88
Libraries/LibWeb/HTML/AttributeNames.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/AttributeNames.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
namespace AttributeNames {
|
||||
|
||||
#define __ENUMERATE_HTML_ATTRIBUTE(name) FlyString name;
|
||||
ENUMERATE_HTML_ATTRIBUTES
|
||||
#undef __ENUMERATE_HTML_ATTRIBUTE
|
||||
|
||||
void initialize_strings()
|
||||
{
|
||||
static bool s_initialized = false;
|
||||
VERIFY(!s_initialized);
|
||||
|
||||
#define __ENUMERATE_HTML_ATTRIBUTE(name) \
|
||||
name = #name##_fly_string;
|
||||
ENUMERATE_HTML_ATTRIBUTES
|
||||
#undef __ENUMERATE_HTML_ATTRIBUTE
|
||||
|
||||
// NOTE: Special cases for C++ keywords.
|
||||
class_ = "class"_fly_string;
|
||||
for_ = "for"_fly_string;
|
||||
default_ = "default"_fly_string;
|
||||
char_ = "char"_fly_string;
|
||||
|
||||
// NOTE: Special cases for attributes with dashes in them.
|
||||
accept_charset = "accept-charset"_fly_string;
|
||||
http_equiv = "http-equiv"_fly_string;
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/#boolean-attribute
|
||||
bool is_boolean_attribute(FlyString const& attribute)
|
||||
{
|
||||
// NOTE: For web compatibility, this matches the list of attributes which Chromium considers to be booleans,
|
||||
// excluding attributes that are only used by Chromium itself:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/460b7c003cf89fc9493e721701906f19e5f6a387:chrome/test/chromedriver/element_commands.cc;l=48-94
|
||||
return attribute.equals_ignoring_ascii_case(AttributeNames::allowfullscreen)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::async)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::autofocus)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::autoplay)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::checked)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::compact)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::controls)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::declare)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::default_)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::defaultchecked)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::defaultselected)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::defer)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::disabled)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::ended)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::formnovalidate)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::hidden)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::indeterminate)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::iscontenteditable)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::ismap)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::itemscope)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::loop)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::multiple)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::muted)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::nohref)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::nomodule)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::noresize)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::noshade)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::novalidate)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::nowrap)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::open)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::paused)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::playsinline)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::readonly)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::required)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::reversed)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::seeking)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::selected)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::truespeed)
|
||||
|| attribute.equals_ignoring_ascii_case(AttributeNames::willvalidate);
|
||||
}
|
||||
|
||||
}
|
288
Libraries/LibWeb/HTML/AttributeNames.h
Normal file
288
Libraries/LibWeb/HTML/AttributeNames.h
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/FlyString.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
namespace AttributeNames {
|
||||
|
||||
#define ENUMERATE_HTML_ATTRIBUTES \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(abbr) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(accept) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(accept_charset) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(accesskey) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(action) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(align) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(alink) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(allow) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(allowfullscreen) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(alt) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(archive) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(async) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(as) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(autocomplete) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(autofocus) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(autoplay) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(axis) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(background) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(behavior) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(bgcolor) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(border) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(cellpadding) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(cellspacing) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(char_) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(charoff) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(charset) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(checked) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(cite) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(class_) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(classid) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(clear) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(code) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(codebase) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(codetype) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(color) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(cols) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(colspan) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(compact) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(content) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(contenteditable) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(controls) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(coords) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(crossorigin) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(data) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(datetime) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(declare) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(default_) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(defaultchecked) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(defaultselected) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(defer) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(dir) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(direction) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(dirname) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(disabled) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(download) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(enctype) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ended) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(event) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(face) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(fetchpriority) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(for_) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(form) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(formaction) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(formenctype) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(formmethod) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(formnovalidate) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(formtarget) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(frame) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(frameborder) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(headers) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(height) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(hidden) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(high) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(href) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(hreflang) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(hspace) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(http_equiv) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(id) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(imagesizes) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(imagesrcset) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(indeterminate) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(inert) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(integrity) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(is) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(iscontenteditable) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ismap) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(itemscope) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(kind) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(label) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(lang) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(language) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(link) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(list) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(loading) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(longdesc) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(loop) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(low) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(lowsrc) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(marginheight) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(marginwidth) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(max) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(maxlength) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(media) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(method) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(min) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(minlength) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(multiple) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(muted) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(name) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(nohref) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(nomodule) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(nonce) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(noresize) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(noshade) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(novalidate) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(nowrap) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onabort) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onafterprint) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onauxclick) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onbeforeprint) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onbeforeunload) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onblur) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncancel) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncanplay) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncanplaythrough) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onchange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onclick) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onclose) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncontextmenu) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oncuechange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondblclick) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondrag) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondragend) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondragenter) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondragleave) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondragover) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondragstart) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondrop) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ondurationchange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onemptied) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onended) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onerror) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onfocus) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onfocusin) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onfocusout) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onformdata) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onhashchange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oninput) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(oninvalid) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onkeydown) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onkeypress) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onkeyup) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onlanguagechange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onload) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onloadeddata) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onloadedmetadata) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onloadstart) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmessage) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmessageerror) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmousedown) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmouseenter) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmouseleave) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmousemove) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmouseout) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmouseover) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onmouseup) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onoffline) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ononline) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onpagehide) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onpageshow) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onpause) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onplay) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onplaying) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onpopstate) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onprogress) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onratechange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onrejectionhandled) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onreset) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onresize) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onscroll) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onsecuritypolicyviolation) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onseeked) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onseeking) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onselect) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onselectionchange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onslotchange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onstalled) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onstorage) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onsubmit) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onsuspend) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ontimeupdate) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ontoggle) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onunhandledrejection) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onunload) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onvolumechange) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onwaiting) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onwebkitanimationend) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onwebkitanimationiteration) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onwebkitanimationstart) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onwebkittransitionend) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(onwheel) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(open) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(optimum) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(pattern) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(paused) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(ping) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(placeholder) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(playsinline) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(popover) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(poster) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(preload) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(readonly) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(referrerpolicy) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(rel) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(required) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(rev) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(reversed) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(rows) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(rowspan) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(rules) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(scheme) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(scope) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(scrollamount) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(scrolldelay) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(scrolling) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(seeking) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(selected) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(shadowrootclonable) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(shadowrootdelegatesfocus) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(shadowrootmode) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(shadowrootserializable) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(shape) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(size) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(sizes) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(slot) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(span) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(src) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(srcdoc) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(srclang) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(srcset) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(standby) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(start) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(step) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(style) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(summary) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(tabindex) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(target) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(text) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(title) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(truespeed) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(type) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(usemap) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(valign) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(value) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(valuetype) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(version) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(vlink) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(vspace) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(width) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(willvalidate) \
|
||||
__ENUMERATE_HTML_ATTRIBUTE(wrap)
|
||||
|
||||
#define __ENUMERATE_HTML_ATTRIBUTE(name) extern FlyString name;
|
||||
ENUMERATE_HTML_ATTRIBUTES
|
||||
#undef __ENUMERATE_HTML_ATTRIBUTE
|
||||
|
||||
void initialize_strings();
|
||||
|
||||
}
|
||||
|
||||
bool is_boolean_attribute(FlyString const& attribute);
|
||||
|
||||
}
|
35
Libraries/LibWeb/HTML/AudioPlayState.h
Normal file
35
Libraries/LibWeb/HTML/AudioPlayState.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
enum class AudioPlayState {
|
||||
Paused,
|
||||
Playing,
|
||||
};
|
||||
|
||||
enum class MuteState {
|
||||
Muted,
|
||||
Unmuted,
|
||||
};
|
||||
|
||||
constexpr MuteState invert_mute_state(MuteState mute_state)
|
||||
{
|
||||
switch (mute_state) {
|
||||
case MuteState::Muted:
|
||||
return MuteState::Unmuted;
|
||||
case MuteState::Unmuted:
|
||||
return MuteState::Muted;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
119
Libraries/LibWeb/HTML/AudioTrack.cpp
Normal file
119
Libraries/LibWeb/HTML/AudioTrack.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/IDAllocator.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibMedia/Audio/Loader.h>
|
||||
#include <LibWeb/Bindings/AudioTrackPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/HTML/AudioTrack.h>
|
||||
#include <LibWeb/HTML/AudioTrackList.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/HTMLMediaElement.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
#include <LibWeb/Platform/AudioCodecPlugin.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(AudioTrack);
|
||||
|
||||
static IDAllocator s_audio_track_id_allocator;
|
||||
|
||||
AudioTrack::AudioTrack(JS::Realm& realm, JS::NonnullGCPtr<HTMLMediaElement> media_element, NonnullRefPtr<Audio::Loader> loader)
|
||||
: PlatformObject(realm)
|
||||
, m_media_element(media_element)
|
||||
, m_audio_plugin(Platform::AudioCodecPlugin::create(move(loader)).release_value_but_fixme_should_propagate_errors())
|
||||
{
|
||||
m_audio_plugin->on_playback_position_updated = [this](auto position) {
|
||||
if (auto* paintable = m_media_element->paintable())
|
||||
paintable->set_needs_display();
|
||||
|
||||
auto playback_position = static_cast<double>(position.to_milliseconds()) / 1000.0;
|
||||
m_media_element->set_current_playback_position(playback_position);
|
||||
};
|
||||
|
||||
m_audio_plugin->on_decoder_error = [this](String error_message) {
|
||||
m_media_element->set_decoder_error(move(error_message));
|
||||
};
|
||||
}
|
||||
|
||||
AudioTrack::~AudioTrack()
|
||||
{
|
||||
auto id = m_id.to_number<int>();
|
||||
VERIFY(id.has_value());
|
||||
|
||||
s_audio_track_id_allocator.deallocate(id.value());
|
||||
}
|
||||
|
||||
void AudioTrack::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(AudioTrack);
|
||||
|
||||
auto id = s_audio_track_id_allocator.allocate();
|
||||
m_id = String::number(id);
|
||||
}
|
||||
|
||||
void AudioTrack::play(Badge<HTMLAudioElement>)
|
||||
{
|
||||
m_audio_plugin->resume_playback();
|
||||
}
|
||||
|
||||
void AudioTrack::pause(Badge<HTMLAudioElement>)
|
||||
{
|
||||
m_audio_plugin->pause_playback();
|
||||
}
|
||||
|
||||
AK::Duration AudioTrack::duration()
|
||||
{
|
||||
return m_audio_plugin->duration();
|
||||
}
|
||||
|
||||
void AudioTrack::seek(double position, MediaSeekMode seek_mode)
|
||||
{
|
||||
// FIXME: Implement seeking mode.
|
||||
(void)seek_mode;
|
||||
|
||||
m_audio_plugin->seek(position);
|
||||
}
|
||||
|
||||
void AudioTrack::update_volume()
|
||||
{
|
||||
m_audio_plugin->set_volume(m_media_element->effective_media_volume());
|
||||
}
|
||||
|
||||
void AudioTrack::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_media_element);
|
||||
visitor.visit(m_audio_track_list);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-enabled
|
||||
void AudioTrack::set_enabled(bool enabled)
|
||||
{
|
||||
// On setting, it must enable the track if the new value is true, and disable it otherwise. (If the track is no
|
||||
// longer in an AudioTrackList object, then the track being enabled or disabled has no effect beyond changing the
|
||||
// value of the attribute on the AudioTrack object.)
|
||||
if (m_enabled == enabled)
|
||||
return;
|
||||
|
||||
if (m_audio_track_list) {
|
||||
// Whenever an audio track in an AudioTrackList that was disabled is enabled, and whenever one that was enabled
|
||||
// is disabled, the user agent must queue a media element task given the media element to fire an event named
|
||||
// change at the AudioTrackList object.
|
||||
m_media_element->queue_a_media_element_task([this]() {
|
||||
m_audio_track_list->dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change));
|
||||
});
|
||||
}
|
||||
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
68
Libraries/LibWeb/HTML/AudioTrack.h
Normal file
68
Libraries/LibWeb/HTML/AudioTrack.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibMedia/Audio/Forward.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class AudioTrack final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(AudioTrack, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(AudioTrack);
|
||||
|
||||
public:
|
||||
virtual ~AudioTrack() override;
|
||||
|
||||
void set_audio_track_list(Badge<AudioTrackList>, JS::GCPtr<AudioTrackList> audio_track_list) { m_audio_track_list = audio_track_list; }
|
||||
|
||||
void play(Badge<HTMLAudioElement>);
|
||||
void pause(Badge<HTMLAudioElement>);
|
||||
|
||||
AK::Duration duration();
|
||||
void seek(double, MediaSeekMode);
|
||||
|
||||
void update_volume();
|
||||
|
||||
String const& id() const { return m_id; }
|
||||
String const& kind() const { return m_kind; }
|
||||
String const& label() const { return m_label; }
|
||||
String const& language() const { return m_language; }
|
||||
|
||||
bool enabled() const { return m_enabled; }
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
private:
|
||||
AudioTrack(JS::Realm&, JS::NonnullGCPtr<HTMLMediaElement>, NonnullRefPtr<Audio::Loader>);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-id
|
||||
String m_id;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-kind
|
||||
String m_kind;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-label
|
||||
String m_label;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-language
|
||||
String m_language;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-enabled
|
||||
bool m_enabled { false };
|
||||
|
||||
JS::NonnullGCPtr<HTMLMediaElement> m_media_element;
|
||||
JS::GCPtr<AudioTrackList> m_audio_track_list;
|
||||
|
||||
NonnullOwnPtr<Platform::AudioCodecPlugin> m_audio_plugin;
|
||||
};
|
||||
|
||||
}
|
9
Libraries/LibWeb/HTML/AudioTrack.idl
Normal file
9
Libraries/LibWeb/HTML/AudioTrack.idl
Normal file
|
@ -0,0 +1,9 @@
|
|||
// https://html.spec.whatwg.org/multipage/media.html#audiotrack
|
||||
[Exposed=Window]
|
||||
interface AudioTrack {
|
||||
readonly attribute DOMString id;
|
||||
readonly attribute DOMString kind;
|
||||
readonly attribute DOMString label;
|
||||
readonly attribute DOMString language;
|
||||
attribute boolean enabled;
|
||||
};
|
126
Libraries/LibWeb/HTML/AudioTrackList.cpp
Normal file
126
Libraries/LibWeb/HTML/AudioTrackList.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/Bindings/AudioTrackListPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/AudioTrackList.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(AudioTrackList);
|
||||
|
||||
AudioTrackList::AudioTrackList(JS::Realm& realm)
|
||||
: DOM::EventTarget(realm, MayInterfereWithIndexedPropertyAccess::Yes)
|
||||
{
|
||||
}
|
||||
|
||||
void AudioTrackList::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(AudioTrackList);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-tracklist-item
|
||||
JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> AudioTrackList::internal_get_own_property(JS::PropertyKey const& property_name) const
|
||||
{
|
||||
// To determine the value of an indexed property for a given index index in an AudioTrackList or VideoTrackList
|
||||
// object list, the user agent must return the AudioTrack or VideoTrack object that represents the indexth track
|
||||
// in list.
|
||||
if (property_name.is_number()) {
|
||||
if (auto index = property_name.as_number(); index < m_audio_tracks.size()) {
|
||||
JS::PropertyDescriptor descriptor;
|
||||
descriptor.value = m_audio_tracks.at(index);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
return Base::internal_get_own_property(property_name);
|
||||
}
|
||||
|
||||
void AudioTrackList::add_track(Badge<HTMLMediaElement>, JS::NonnullGCPtr<AudioTrack> audio_track)
|
||||
{
|
||||
m_audio_tracks.append(audio_track);
|
||||
audio_track->set_audio_track_list({}, this);
|
||||
}
|
||||
|
||||
void AudioTrackList::remove_all_tracks(Badge<HTMLMediaElement>)
|
||||
{
|
||||
m_audio_tracks.clear();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotracklist-gettrackbyid
|
||||
JS::GCPtr<AudioTrack> AudioTrackList::get_track_by_id(StringView id) const
|
||||
{
|
||||
// The AudioTrackList getTrackById(id) and VideoTrackList getTrackById(id) methods must return the first AudioTrack
|
||||
// or VideoTrack object (respectively) in the AudioTrackList or VideoTrackList object (respectively) whose identifier
|
||||
// is equal to the value of the id argument (in the natural order of the list, as defined above).
|
||||
auto it = m_audio_tracks.find_if([&](auto const& audio_track) {
|
||||
return audio_track->id() == id;
|
||||
});
|
||||
|
||||
// When no tracks match the given argument, the methods must return null.
|
||||
if (it == m_audio_tracks.end())
|
||||
return nullptr;
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
bool AudioTrackList::has_enabled_track() const
|
||||
{
|
||||
auto it = m_audio_tracks.find_if([&](auto const& audio_track) {
|
||||
return audio_track->enabled();
|
||||
});
|
||||
|
||||
return it != m_audio_tracks.end();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onchange
|
||||
void AudioTrackList::set_onchange(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::change, event_handler);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onchange
|
||||
WebIDL::CallbackType* AudioTrackList::onchange()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::change);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onaddtrack
|
||||
void AudioTrackList::set_onaddtrack(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::addtrack, event_handler);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onaddtrack
|
||||
WebIDL::CallbackType* AudioTrackList::onaddtrack()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::addtrack);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onremovetrack
|
||||
void AudioTrackList::set_onremovetrack(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::removetrack, event_handler);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#handler-tracklist-onremovetrack
|
||||
WebIDL::CallbackType* AudioTrackList::onremovetrack()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::removetrack);
|
||||
}
|
||||
|
||||
void AudioTrackList::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_audio_tracks);
|
||||
}
|
||||
|
||||
}
|
60
Libraries/LibWeb/HTML/AudioTrackList.h
Normal file
60
Libraries/LibWeb/HTML/AudioTrackList.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibJS/Heap/MarkedVector.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/HTML/AudioTrack.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class AudioTrackList final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(AudioTrackList, DOM::EventTarget);
|
||||
JS_DECLARE_ALLOCATOR(AudioTrackList);
|
||||
|
||||
public:
|
||||
void add_track(Badge<HTMLMediaElement>, JS::NonnullGCPtr<AudioTrack>);
|
||||
void remove_all_tracks(Badge<HTMLMediaElement>);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotracklist-length
|
||||
size_t length() const { return m_audio_tracks.size(); }
|
||||
|
||||
JS::GCPtr<AudioTrack> get_track_by_id(StringView id) const;
|
||||
bool has_enabled_track() const;
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_enabled_track(Callback&& callback)
|
||||
{
|
||||
for (auto& audio_track : m_audio_tracks) {
|
||||
if (audio_track->enabled())
|
||||
callback(*audio_track);
|
||||
}
|
||||
}
|
||||
|
||||
void set_onchange(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onchange();
|
||||
|
||||
void set_onaddtrack(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onaddtrack();
|
||||
|
||||
void set_onremovetrack(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onremovetrack();
|
||||
|
||||
private:
|
||||
explicit AudioTrackList(JS::Realm&);
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyKey const& property_name) const override;
|
||||
|
||||
Vector<JS::NonnullGCPtr<AudioTrack>> m_audio_tracks;
|
||||
};
|
||||
|
||||
}
|
15
Libraries/LibWeb/HTML/AudioTrackList.idl
Normal file
15
Libraries/LibWeb/HTML/AudioTrackList.idl
Normal file
|
@ -0,0 +1,15 @@
|
|||
#import <DOM/EventHandler.idl>
|
||||
#import <DOM/EventTarget.idl>
|
||||
#import <HTML/AudioTrack.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/media.html#audiotracklist
|
||||
[Exposed=Window]
|
||||
interface AudioTrackList : EventTarget {
|
||||
readonly attribute unsigned long length;
|
||||
getter AudioTrack (unsigned long index);
|
||||
AudioTrack? getTrackById(DOMString id);
|
||||
|
||||
attribute EventHandler onchange;
|
||||
attribute EventHandler onaddtrack;
|
||||
attribute EventHandler onremovetrack;
|
||||
};
|
33
Libraries/LibWeb/HTML/BeforeUnloadEvent.cpp
Normal file
33
Libraries/LibWeb/HTML/BeforeUnloadEvent.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/BeforeUnloadEventPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/BeforeUnloadEvent.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(BeforeUnloadEvent);
|
||||
|
||||
JS::NonnullGCPtr<BeforeUnloadEvent> BeforeUnloadEvent::create(JS::Realm& realm, FlyString const& event_name, DOM::EventInit const& event_init)
|
||||
{
|
||||
return realm.heap().allocate<BeforeUnloadEvent>(realm, realm, event_name, event_init);
|
||||
}
|
||||
|
||||
BeforeUnloadEvent::BeforeUnloadEvent(JS::Realm& realm, FlyString const& event_name, DOM::EventInit const& event_init)
|
||||
: DOM::Event(realm, event_name, event_init)
|
||||
{
|
||||
}
|
||||
|
||||
BeforeUnloadEvent::~BeforeUnloadEvent() = default;
|
||||
|
||||
void BeforeUnloadEvent::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(BeforeUnloadEvent);
|
||||
}
|
||||
|
||||
}
|
34
Libraries/LibWeb/HTML/BeforeUnloadEvent.h
Normal file
34
Libraries/LibWeb/HTML/BeforeUnloadEvent.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FlyString.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class BeforeUnloadEvent final : public DOM::Event {
|
||||
WEB_PLATFORM_OBJECT(BeforeUnloadEvent, DOM::Event);
|
||||
JS_DECLARE_ALLOCATOR(BeforeUnloadEvent);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static JS::NonnullGCPtr<BeforeUnloadEvent> create(JS::Realm&, FlyString const& event_name, DOM::EventInit const& = {});
|
||||
|
||||
BeforeUnloadEvent(JS::Realm&, FlyString const& event_name, DOM::EventInit const&);
|
||||
|
||||
virtual ~BeforeUnloadEvent() override;
|
||||
|
||||
String const& return_value() const { return m_return_value; }
|
||||
void set_return_value(String const& return_value) { m_return_value = return_value; }
|
||||
|
||||
private:
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
String m_return_value;
|
||||
};
|
||||
|
||||
}
|
7
Libraries/LibWeb/HTML/BeforeUnloadEvent.idl
Normal file
7
Libraries/LibWeb/HTML/BeforeUnloadEvent.idl
Normal file
|
@ -0,0 +1,7 @@
|
|||
#import <DOM/Event.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-beforeunloadevent-interface
|
||||
[Exposed=Window]
|
||||
interface BeforeUnloadEvent : Event {
|
||||
attribute DOMString returnValue;
|
||||
};
|
72
Libraries/LibWeb/HTML/BroadcastChannel.cpp
Normal file
72
Libraries/LibWeb/HTML/BroadcastChannel.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/Bindings/BroadcastChannelPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(BroadcastChannel);
|
||||
|
||||
JS::NonnullGCPtr<BroadcastChannel> BroadcastChannel::construct_impl(JS::Realm& realm, FlyString const& name)
|
||||
{
|
||||
return realm.heap().allocate<BroadcastChannel>(realm, realm, name);
|
||||
}
|
||||
|
||||
BroadcastChannel::BroadcastChannel(JS::Realm& realm, FlyString const& name)
|
||||
: DOM::EventTarget(realm)
|
||||
, m_channel_name(name)
|
||||
{
|
||||
}
|
||||
|
||||
void BroadcastChannel::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(BroadcastChannel);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-name
|
||||
FlyString BroadcastChannel::name()
|
||||
{
|
||||
// The name getter steps are to return this's channel name.
|
||||
return m_channel_name;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-close
|
||||
void BroadcastChannel::close()
|
||||
{
|
||||
// The close() method steps are to set this's closed flag to true.
|
||||
m_closed_flag = true;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
|
||||
void BroadcastChannel::set_onmessage(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::message, event_handler);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
|
||||
WebIDL::CallbackType* BroadcastChannel::onmessage()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::message);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
|
||||
void BroadcastChannel::set_onmessageerror(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::messageerror, event_handler);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
|
||||
WebIDL::CallbackType* BroadcastChannel::onmessageerror()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::messageerror);
|
||||
}
|
||||
|
||||
}
|
38
Libraries/LibWeb/HTML/BroadcastChannel.h
Normal file
38
Libraries/LibWeb/HTML/BroadcastChannel.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class BroadcastChannel final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(BroadcastChannel, DOM::EventTarget);
|
||||
JS_DECLARE_ALLOCATOR(BroadcastChannel);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static JS::NonnullGCPtr<BroadcastChannel> construct_impl(JS::Realm&, FlyString const& name);
|
||||
|
||||
FlyString name();
|
||||
|
||||
void close();
|
||||
|
||||
void set_onmessage(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onmessage();
|
||||
void set_onmessageerror(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onmessageerror();
|
||||
|
||||
private:
|
||||
BroadcastChannel(JS::Realm&, FlyString const& name);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
FlyString m_channel_name;
|
||||
bool m_closed_flag { false };
|
||||
};
|
||||
|
||||
}
|
14
Libraries/LibWeb/HTML/BroadcastChannel.idl
Normal file
14
Libraries/LibWeb/HTML/BroadcastChannel.idl
Normal file
|
@ -0,0 +1,14 @@
|
|||
#import <DOM/EventHandler.idl>
|
||||
#import <DOM/EventTarget.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#broadcastchannel
|
||||
[Exposed=(Window,Worker)]
|
||||
interface BroadcastChannel : EventTarget {
|
||||
constructor(DOMString name);
|
||||
|
||||
readonly attribute DOMString name;
|
||||
[FIXME] undefined postMessage(any message);
|
||||
undefined close();
|
||||
attribute EventHandler onmessage;
|
||||
attribute EventHandler onmessageerror;
|
||||
};
|
519
Libraries/LibWeb/HTML/BrowsingContext.cpp
Normal file
519
Libraries/LibWeb/HTML/BrowsingContext.cpp
Normal file
|
@ -0,0 +1,519 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/HTMLCollection.h>
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/DOMURL/DOMURL.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/BrowsingContextGroup.h>
|
||||
#include <LibWeb/HTML/CrossOrigin/OpenerPolicy.h>
|
||||
#include <LibWeb/HTML/DocumentState.h>
|
||||
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
||||
#include <LibWeb/HTML/HTMLDocument.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/NavigableContainer.h>
|
||||
#include <LibWeb/HTML/SandboxingFlagSet.h>
|
||||
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/WindowProxy.h>
|
||||
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
|
||||
#include <LibWeb/Layout/BreakNode.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(BrowsingContext);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#matches-about:blank
|
||||
bool url_matches_about_blank(URL::URL const& url)
|
||||
{
|
||||
// A URL matches about:blank if its scheme is "about", its path contains a single string "blank", its username and password are the empty string, and its host is null.
|
||||
return url.scheme() == "about"sv
|
||||
&& url.paths().size() == 1 && url.paths()[0] == "blank"sv
|
||||
&& url.username().is_empty()
|
||||
&& url.password().is_empty()
|
||||
&& url.host().has<Empty>();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#matches-about:srcdoc
|
||||
bool url_matches_about_srcdoc(URL::URL const& url)
|
||||
{
|
||||
// A URL matches about:srcdoc if its scheme is "about", its path contains a single string "srcdoc", its query is null, its username and password are the empty string, and its host is null.
|
||||
return url.scheme() == "about"sv
|
||||
&& url.paths().size() == 1 && url.paths()[0] == "srcdoc"sv
|
||||
&& !url.query().has_value()
|
||||
&& url.username().is_empty()
|
||||
&& url.password().is_empty()
|
||||
&& url.host().has<Empty>();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#determining-the-origin
|
||||
URL::Origin determine_the_origin(Optional<URL::URL> const& url, SandboxingFlagSet sandbox_flags, Optional<URL::Origin> source_origin)
|
||||
{
|
||||
// 1. If sandboxFlags has its sandboxed origin browsing context flag set, then return a new opaque origin.
|
||||
if (has_flag(sandbox_flags, SandboxingFlagSet::SandboxedOrigin)) {
|
||||
return URL::Origin {};
|
||||
}
|
||||
|
||||
// 2. If url is null, then return a new opaque origin.
|
||||
if (!url.has_value()) {
|
||||
return URL::Origin {};
|
||||
}
|
||||
|
||||
// 3. If url is about:srcdoc, then:
|
||||
if (url == "about:srcdoc"sv) {
|
||||
// 1. Assert: sourceOrigin is non-null.
|
||||
VERIFY(source_origin.has_value());
|
||||
|
||||
// 2. Return sourceOrigin.
|
||||
return source_origin.release_value();
|
||||
}
|
||||
|
||||
// 4. If url matches about:blank and sourceOrigin is non-null, then return sourceOrigin.
|
||||
if (url_matches_about_blank(*url) && source_origin.has_value())
|
||||
return source_origin.release_value();
|
||||
|
||||
// 5. Return url's origin.
|
||||
return url->origin();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#creating-a-new-auxiliary-browsing-context
|
||||
WebIDL::ExceptionOr<BrowsingContext::BrowsingContextAndDocument> BrowsingContext::create_a_new_auxiliary_browsing_context_and_document(JS::NonnullGCPtr<Page> page, JS::NonnullGCPtr<HTML::BrowsingContext> opener)
|
||||
{
|
||||
// 1. Let openerTopLevelBrowsingContext be opener's top-level traversable's active browsing context.
|
||||
auto opener_top_level_browsing_context = opener->top_level_traversable()->active_browsing_context();
|
||||
|
||||
// 2. Let group be openerTopLevelBrowsingContext's group.
|
||||
auto group = opener_top_level_browsing_context->group();
|
||||
|
||||
// 3. Assert: group is non-null, as navigating invokes this directly.
|
||||
VERIFY(group);
|
||||
|
||||
// 4. Set browsingContext and document be the result of creating a new browsing context and document with opener's active document, null, and group.
|
||||
auto [browsing_context, document] = TRY(create_a_new_browsing_context_and_document(page, opener->active_document(), nullptr, *group));
|
||||
|
||||
// 5. Set browsingContext's is auxiliary to true.
|
||||
browsing_context->m_is_auxiliary = true;
|
||||
|
||||
// 6. Append browsingContext to group.
|
||||
group->append(browsing_context);
|
||||
|
||||
// 7. Set browsingContext's opener browsing context to opener.
|
||||
browsing_context->set_opener_browsing_context(opener);
|
||||
|
||||
// 8. Set browsingContext's virtual browsing context group ID to openerTopLevelBrowsingContext's virtual browsing context group ID.
|
||||
browsing_context->m_virtual_browsing_context_group_id = opener_top_level_browsing_context->m_virtual_browsing_context_group_id;
|
||||
|
||||
// 9. Set browsingContext's opener origin at creation to opener's active document's origin.
|
||||
browsing_context->m_opener_origin_at_creation = opener->active_document()->origin();
|
||||
|
||||
// 10. Return browsingContext and document.
|
||||
return BrowsingContext::BrowsingContextAndDocument { browsing_context, document };
|
||||
}
|
||||
|
||||
static void populate_with_html_head_body(JS::NonnullGCPtr<DOM::Document> document)
|
||||
{
|
||||
auto html_node = MUST(DOM::create_element(document, HTML::TagNames::html, Namespace::HTML));
|
||||
auto head_element = MUST(DOM::create_element(document, HTML::TagNames::head, Namespace::HTML));
|
||||
MUST(html_node->append_child(head_element));
|
||||
auto body_element = MUST(DOM::create_element(document, HTML::TagNames::body, Namespace::HTML));
|
||||
MUST(html_node->append_child(body_element));
|
||||
MUST(document->append_child(html_node));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#creating-a-new-browsing-context
|
||||
WebIDL::ExceptionOr<BrowsingContext::BrowsingContextAndDocument> BrowsingContext::create_a_new_browsing_context_and_document(JS::NonnullGCPtr<Page> page, JS::GCPtr<DOM::Document> creator, JS::GCPtr<DOM::Element> embedder, JS::NonnullGCPtr<BrowsingContextGroup> group)
|
||||
{
|
||||
auto& vm = group->vm();
|
||||
|
||||
// 1. Let browsingContext be a new browsing context.
|
||||
JS::NonnullGCPtr<BrowsingContext> browsing_context = *vm.heap().allocate_without_realm<BrowsingContext>(page);
|
||||
|
||||
// 2. Let unsafeContextCreationTime be the unsafe shared current time.
|
||||
[[maybe_unused]] auto unsafe_context_creation_time = HighResolutionTime::unsafe_shared_current_time();
|
||||
|
||||
// 3. Let creatorOrigin be null.
|
||||
Optional<URL::Origin> creator_origin = {};
|
||||
|
||||
// 4. Let creatorBaseURL be null.
|
||||
Optional<URL::URL> creator_base_url = {};
|
||||
|
||||
// 5. If creator is non-null, then:
|
||||
if (creator) {
|
||||
// 1. Set creatorOrigin to creator's origin.
|
||||
creator_origin = creator->origin();
|
||||
|
||||
// 2. Set creatorBaseURL to creator's document base URL.
|
||||
creator_base_url = creator->base_url();
|
||||
|
||||
// 3. Set browsingContext's virtual browsing context group ID to creator's browsing context's top-level browsing context's virtual browsing context group ID.
|
||||
VERIFY(creator->browsing_context());
|
||||
browsing_context->m_virtual_browsing_context_group_id = creator->browsing_context()->top_level_browsing_context()->m_virtual_browsing_context_group_id;
|
||||
}
|
||||
|
||||
// 6. Let sandboxFlags be the result of determining the creation sandboxing flags given browsingContext and embedder.
|
||||
SandboxingFlagSet sandbox_flags = {};
|
||||
|
||||
// 7. Let origin be the result of determining the origin given about:blank, sandboxFlags, and creatorOrigin.
|
||||
auto origin = determine_the_origin(URL::URL("about:blank"sv), sandbox_flags, creator_origin);
|
||||
|
||||
// FIXME: 8. Let permissionsPolicy be the result of creating a permissions policy given embedder and origin. [PERMISSIONSPOLICY]
|
||||
|
||||
// FIXME: 9. Let agent be the result of obtaining a similar-origin window agent given origin, group, and false.
|
||||
|
||||
JS::GCPtr<Window> window;
|
||||
|
||||
// 10. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations:
|
||||
auto realm_execution_context = Bindings::create_a_new_javascript_realm(
|
||||
Bindings::main_thread_vm(),
|
||||
[&](JS::Realm& realm) -> JS::Object* {
|
||||
auto window_proxy = realm.heap().allocate<WindowProxy>(realm, realm);
|
||||
browsing_context->set_window_proxy(window_proxy);
|
||||
|
||||
// - For the global object, create a new Window object.
|
||||
window = Window::create(realm);
|
||||
return window.ptr();
|
||||
},
|
||||
[&](JS::Realm&) -> JS::Object* {
|
||||
// - For the global this binding, use browsingContext's WindowProxy object.
|
||||
return browsing_context->window_proxy();
|
||||
});
|
||||
|
||||
// 11. Let topLevelCreationURL be about:blank if embedder is null; otherwise embedder's relevant settings object's top-level creation URL.
|
||||
auto top_level_creation_url = !embedder ? URL::URL("about:blank") : relevant_settings_object(*embedder).top_level_creation_url;
|
||||
|
||||
// 12. Let topLevelOrigin be origin if embedder is null; otherwise embedder's relevant settings object's top-level origin.
|
||||
auto top_level_origin = !embedder ? origin : relevant_settings_object(*embedder).origin();
|
||||
|
||||
// 13. Set up a window environment settings object with about:blank, realm execution context, null, topLevelCreationURL, and topLevelOrigin.
|
||||
WindowEnvironmentSettingsObject::setup(
|
||||
page,
|
||||
URL::URL("about:blank"),
|
||||
move(realm_execution_context),
|
||||
{},
|
||||
top_level_creation_url,
|
||||
top_level_origin);
|
||||
|
||||
// 14. Let loadTimingInfo be a new document load timing info with its navigation start time set to the result of calling
|
||||
// coarsen time with unsafeContextCreationTime and the new environment settings object's cross-origin isolated capability.
|
||||
auto load_timing_info = DOM::DocumentLoadTimingInfo();
|
||||
load_timing_info.navigation_start_time = HighResolutionTime::coarsen_time(
|
||||
unsafe_context_creation_time,
|
||||
verify_cast<WindowEnvironmentSettingsObject>(Bindings::principal_host_defined_environment_settings_object(window->realm())).cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::Yes);
|
||||
|
||||
// 15. Let document be a new Document, with:
|
||||
auto document = HTML::HTMLDocument::create(window->realm());
|
||||
|
||||
// Non-standard
|
||||
window->set_associated_document(*document);
|
||||
|
||||
// type: "html"
|
||||
document->set_document_type(DOM::Document::Type::HTML);
|
||||
|
||||
// content type: "text/html"
|
||||
document->set_content_type("text/html"_string);
|
||||
|
||||
// mode: "quirks"
|
||||
document->set_quirks_mode(DOM::QuirksMode::Yes);
|
||||
|
||||
// origin: origin
|
||||
document->set_origin(origin);
|
||||
|
||||
// browsing context: browsingContext
|
||||
document->set_browsing_context(browsing_context);
|
||||
|
||||
// FIXME: permissions policy: permissionsPolicy
|
||||
|
||||
// FIXME: active sandboxing flag set: sandboxFlags
|
||||
|
||||
// load timing info: loadTimingInfo
|
||||
document->set_load_timing_info(load_timing_info);
|
||||
|
||||
// is initial about:blank: true
|
||||
document->set_is_initial_about_blank(true);
|
||||
// Spec issue: https://github.com/whatwg/html/issues/10261
|
||||
document->set_ready_to_run_scripts();
|
||||
|
||||
// about base URL: creatorBaseURL
|
||||
document->set_about_base_url(creator_base_url);
|
||||
|
||||
// allow declarative shadow roots: true
|
||||
document->set_allow_declarative_shadow_roots(true);
|
||||
|
||||
// 16. If creator is non-null, then:
|
||||
if (creator) {
|
||||
// 1. Set document's referrer to the serialization of creator's URL.
|
||||
document->set_referrer(MUST(String::from_byte_string(creator->url().serialize())));
|
||||
|
||||
// 2. Set document's policy container to a clone of creator's policy container.
|
||||
document->set_policy_container(creator->policy_container());
|
||||
|
||||
// 3. If creator's origin is same origin with creator's relevant settings object's top-level origin,
|
||||
if (creator->origin().is_same_origin(creator->relevant_settings_object().top_level_origin)) {
|
||||
// then set document's opener policy to creator's browsing context's top-level browsing context's active document's opener policy.
|
||||
VERIFY(creator->browsing_context());
|
||||
VERIFY(creator->browsing_context()->top_level_browsing_context()->active_document());
|
||||
document->set_opener_policy(creator->browsing_context()->top_level_browsing_context()->active_document()->opener_policy());
|
||||
}
|
||||
}
|
||||
|
||||
// 17. Assert: document's URL and document's relevant settings object's creation URL are about:blank.
|
||||
VERIFY(document->url() == "about:blank"sv);
|
||||
VERIFY(document->relevant_settings_object().creation_url == "about:blank"sv);
|
||||
|
||||
// 18. Mark document as ready for post-load tasks.
|
||||
document->set_ready_for_post_load_tasks(true);
|
||||
|
||||
// 19. Populate with html/head/body given document.
|
||||
populate_with_html_head_body(*document);
|
||||
|
||||
// 20. Make active document.
|
||||
document->make_active();
|
||||
|
||||
// 21. Completely finish loading document.
|
||||
document->completely_finish_loading();
|
||||
|
||||
// 22. Return browsingContext and document.
|
||||
return BrowsingContext::BrowsingContextAndDocument { browsing_context, document };
|
||||
}
|
||||
|
||||
BrowsingContext::BrowsingContext(JS::NonnullGCPtr<Page> page)
|
||||
: m_page(page)
|
||||
{
|
||||
}
|
||||
|
||||
BrowsingContext::~BrowsingContext() = default;
|
||||
|
||||
void BrowsingContext::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
|
||||
visitor.visit(m_page);
|
||||
visitor.visit(m_window_proxy);
|
||||
visitor.visit(m_group);
|
||||
visitor.visit(m_first_child);
|
||||
visitor.visit(m_last_child);
|
||||
visitor.visit(m_next_sibling);
|
||||
visitor.visit(m_previous_sibling);
|
||||
visitor.visit(m_opener_browsing_context);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#bc-traversable
|
||||
JS::NonnullGCPtr<HTML::TraversableNavigable> BrowsingContext::top_level_traversable() const
|
||||
{
|
||||
// A browsing context's top-level traversable is its active document's node navigable's top-level traversable.
|
||||
auto traversable = active_document()->navigable()->top_level_traversable();
|
||||
VERIFY(traversable);
|
||||
VERIFY(traversable->is_top_level_traversable());
|
||||
return *traversable;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#top-level-browsing-context
|
||||
bool BrowsingContext::is_top_level() const
|
||||
{
|
||||
// FIXME: Remove this. The active document's navigable is sometimes null when it shouldn't be, failing assertions.
|
||||
return true;
|
||||
// A top-level browsing context is a browsing context whose active document's node navigable is a traversable navigable.
|
||||
return active_document() != nullptr && active_document()->navigable() != nullptr && active_document()->navigable()->is_traversable();
|
||||
}
|
||||
|
||||
JS::GCPtr<BrowsingContext> BrowsingContext::top_level_browsing_context() const
|
||||
{
|
||||
auto const* start = this;
|
||||
|
||||
// 1. If start's active document is not fully active, then return null.
|
||||
if (!start->active_document()->is_fully_active()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 2. Let navigable be start's active document's node navigable.
|
||||
auto navigable = start->active_document()->navigable();
|
||||
|
||||
// 3. While navigable's parent is not null, set navigable to navigable's parent.
|
||||
while (navigable->parent()) {
|
||||
navigable = navigable->parent();
|
||||
}
|
||||
|
||||
// 4. Return navigable's active browsing context.
|
||||
return navigable->active_browsing_context();
|
||||
}
|
||||
|
||||
DOM::Document const* BrowsingContext::active_document() const
|
||||
{
|
||||
auto* window = active_window();
|
||||
if (!window)
|
||||
return nullptr;
|
||||
return &window->associated_document();
|
||||
}
|
||||
|
||||
DOM::Document* BrowsingContext::active_document()
|
||||
{
|
||||
auto* window = active_window();
|
||||
if (!window)
|
||||
return nullptr;
|
||||
return &window->associated_document();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#active-window
|
||||
HTML::Window* BrowsingContext::active_window()
|
||||
{
|
||||
return m_window_proxy->window();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#active-window
|
||||
HTML::Window const* BrowsingContext::active_window() const
|
||||
{
|
||||
return m_window_proxy->window();
|
||||
}
|
||||
|
||||
HTML::WindowProxy* BrowsingContext::window_proxy()
|
||||
{
|
||||
return m_window_proxy.ptr();
|
||||
}
|
||||
|
||||
HTML::WindowProxy const* BrowsingContext::window_proxy() const
|
||||
{
|
||||
return m_window_proxy.ptr();
|
||||
}
|
||||
|
||||
void BrowsingContext::set_window_proxy(JS::GCPtr<WindowProxy> window_proxy)
|
||||
{
|
||||
m_window_proxy = move(window_proxy);
|
||||
}
|
||||
|
||||
BrowsingContextGroup* BrowsingContext::group()
|
||||
{
|
||||
return m_group;
|
||||
}
|
||||
|
||||
BrowsingContextGroup const* BrowsingContext::group() const
|
||||
{
|
||||
return m_group;
|
||||
}
|
||||
|
||||
void BrowsingContext::set_group(BrowsingContextGroup* group)
|
||||
{
|
||||
m_group = group;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#bcg-remove
|
||||
void BrowsingContext::remove()
|
||||
{
|
||||
// 1. Assert: browsingContext's group is non-null, because a browsing context only gets discarded once.
|
||||
VERIFY(group());
|
||||
|
||||
// 2. Let group be browsingContext's group.
|
||||
JS::NonnullGCPtr<BrowsingContextGroup> group = *this->group();
|
||||
|
||||
// 3. Set browsingContext's group to null.
|
||||
set_group(nullptr);
|
||||
|
||||
// 4. Remove browsingContext from group's browsing context set.
|
||||
group->browsing_context_set().remove(*this);
|
||||
|
||||
// 5. If group's browsing context set is empty, then remove group from the user agent's browsing context group set.
|
||||
// NOTE: This is done by ~BrowsingContextGroup() when the refcount reaches 0.
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#one-permitted-sandboxed-navigator
|
||||
BrowsingContext const* BrowsingContext::the_one_permitted_sandboxed_navigator() const
|
||||
{
|
||||
// FIXME: Implement this.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS::GCPtr<BrowsingContext> BrowsingContext::first_child() const
|
||||
{
|
||||
return m_first_child;
|
||||
}
|
||||
JS::GCPtr<BrowsingContext> BrowsingContext::next_sibling() const
|
||||
{
|
||||
return m_next_sibling;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#ancestor-browsing-context
|
||||
bool BrowsingContext::is_ancestor_of(BrowsingContext const& potential_descendant) const
|
||||
{
|
||||
// A browsing context potentialDescendant is said to be an ancestor of a browsing context potentialAncestor if the following algorithm returns true:
|
||||
|
||||
// 1. Let potentialDescendantDocument be potentialDescendant's active document.
|
||||
auto const* potential_descendant_document = potential_descendant.active_document();
|
||||
|
||||
// 2. If potentialDescendantDocument is not fully active, then return false.
|
||||
if (!potential_descendant_document->is_fully_active())
|
||||
return false;
|
||||
|
||||
// 3. Let ancestorBCs be the list obtained by taking the browsing context of the active document of each member of potentialDescendantDocument's ancestor navigables.
|
||||
for (auto const& ancestor : potential_descendant_document->ancestor_navigables()) {
|
||||
auto ancestor_browsing_context = ancestor->active_browsing_context();
|
||||
|
||||
// 4. If ancestorBCs contains potentialAncestor, then return true.
|
||||
if (ancestor_browsing_context == this)
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#familiar-with
|
||||
bool BrowsingContext::is_familiar_with(BrowsingContext const& other) const
|
||||
{
|
||||
// A browsing context A is familiar with a second browsing context B if the following algorithm returns true:
|
||||
auto const& A = *this;
|
||||
auto const& B = other;
|
||||
|
||||
// 1. If A's active document's origin is same origin with B's active document's origin, then return true.
|
||||
if (A.active_document()->origin().is_same_origin(B.active_document()->origin()))
|
||||
return true;
|
||||
|
||||
// 2. If A's top-level browsing context is B, then return true.
|
||||
if (A.top_level_browsing_context() == &B)
|
||||
return true;
|
||||
|
||||
// 3. If B is an auxiliary browsing context and A is familiar with B's opener browsing context, then return true.
|
||||
if (B.opener_browsing_context() != nullptr && A.is_familiar_with(*B.opener_browsing_context()))
|
||||
return true;
|
||||
|
||||
// 4. If there exists an ancestor browsing context of B whose active document has the same origin as the active document of A, then return true.
|
||||
// NOTE: This includes the case where A is an ancestor browsing context of B.
|
||||
|
||||
// If B's active document is not fully active then it cannot have ancestor browsing context
|
||||
if (!B.active_document()->is_fully_active())
|
||||
return false;
|
||||
|
||||
for (auto const& ancestor : B.active_document()->ancestor_navigables()) {
|
||||
if (ancestor->active_document()->origin().is_same_origin(A.active_document()->origin()))
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#snapshotting-target-snapshot-params
|
||||
SandboxingFlagSet determine_the_creation_sandboxing_flags(BrowsingContext const&, JS::GCPtr<DOM::Element>)
|
||||
{
|
||||
// FIXME: Populate this once we have the proper flag sets on BrowsingContext
|
||||
return {};
|
||||
}
|
||||
|
||||
bool BrowsingContext::has_navigable_been_destroyed() const
|
||||
{
|
||||
auto navigable = active_document()->navigable();
|
||||
return !navigable || navigable->has_been_destroyed();
|
||||
}
|
||||
|
||||
}
|
180
Libraries/LibWeb/HTML/BrowsingContext.h
Normal file
180
Libraries/LibWeb/HTML/BrowsingContext.h
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/Size.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWeb/HTML/NavigableContainer.h>
|
||||
#include <LibWeb/HTML/SandboxingFlagSet.h>
|
||||
#include <LibWeb/HTML/SessionHistoryEntry.h>
|
||||
#include <LibWeb/HTML/TokenizedFeatures.h>
|
||||
#include <LibWeb/HTML/VisibilityState.h>
|
||||
#include <LibWeb/Platform/Timer.h>
|
||||
#include <LibWeb/TreeNode.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class BrowsingContext final : public JS::Cell {
|
||||
JS_CELL(BrowsingContext, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(BrowsingContext);
|
||||
|
||||
public:
|
||||
struct BrowsingContextAndDocument {
|
||||
JS::NonnullGCPtr<BrowsingContext> browsing_context;
|
||||
JS::NonnullGCPtr<DOM::Document> document;
|
||||
};
|
||||
|
||||
static WebIDL::ExceptionOr<BrowsingContextAndDocument> create_a_new_browsing_context_and_document(JS::NonnullGCPtr<Page> page, JS::GCPtr<DOM::Document> creator, JS::GCPtr<DOM::Element> embedder, JS::NonnullGCPtr<BrowsingContextGroup> group);
|
||||
static WebIDL::ExceptionOr<BrowsingContextAndDocument> create_a_new_auxiliary_browsing_context_and_document(JS::NonnullGCPtr<Page> page, JS::NonnullGCPtr<HTML::BrowsingContext> opener);
|
||||
|
||||
virtual ~BrowsingContext() override;
|
||||
|
||||
JS::NonnullGCPtr<HTML::TraversableNavigable> top_level_traversable() const;
|
||||
|
||||
JS::GCPtr<BrowsingContext> first_child() const;
|
||||
JS::GCPtr<BrowsingContext> next_sibling() const;
|
||||
|
||||
bool is_ancestor_of(BrowsingContext const&) const;
|
||||
bool is_familiar_with(BrowsingContext const&) const;
|
||||
|
||||
template<typename Callback>
|
||||
TraversalDecision for_each_in_inclusive_subtree(Callback callback) const
|
||||
{
|
||||
if (callback(*this) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
for (auto child = first_child(); child; child = child->next_sibling()) {
|
||||
if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
TraversalDecision for_each_in_inclusive_subtree(Callback callback)
|
||||
{
|
||||
if (callback(*this) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
for (auto child = first_child(); child; child = child->next_sibling()) {
|
||||
if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
TraversalDecision for_each_in_subtree(Callback callback) const
|
||||
{
|
||||
for (auto child = first_child(); child; child = child->next_sibling()) {
|
||||
if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
TraversalDecision for_each_in_subtree(Callback callback)
|
||||
{
|
||||
for (auto child = first_child(); child; child = child->next_sibling()) {
|
||||
if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
}
|
||||
|
||||
bool is_top_level() const;
|
||||
bool is_auxiliary() const { return m_is_auxiliary; }
|
||||
|
||||
DOM::Document const* active_document() const;
|
||||
DOM::Document* active_document();
|
||||
|
||||
HTML::WindowProxy* window_proxy();
|
||||
HTML::WindowProxy const* window_proxy() const;
|
||||
|
||||
void set_window_proxy(JS::GCPtr<WindowProxy>);
|
||||
|
||||
HTML::Window* active_window();
|
||||
HTML::Window const* active_window() const;
|
||||
|
||||
Page& page() { return m_page; }
|
||||
Page const& page() const { return m_page; }
|
||||
|
||||
u64 virtual_browsing_context_group_id() const { return m_virtual_browsing_context_group_id; }
|
||||
|
||||
JS::GCPtr<BrowsingContext> top_level_browsing_context() const;
|
||||
|
||||
BrowsingContextGroup* group();
|
||||
BrowsingContextGroup const* group() const;
|
||||
void set_group(BrowsingContextGroup*);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#bcg-remove
|
||||
void remove();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#one-permitted-sandboxed-navigator
|
||||
BrowsingContext const* the_one_permitted_sandboxed_navigator() const;
|
||||
|
||||
bool has_navigable_been_destroyed() const;
|
||||
|
||||
JS::GCPtr<BrowsingContext> opener_browsing_context() const { return m_opener_browsing_context; }
|
||||
void set_opener_browsing_context(JS::GCPtr<BrowsingContext> browsing_context) { m_opener_browsing_context = browsing_context; }
|
||||
|
||||
void set_is_popup(TokenizedFeature::Popup is_popup) { m_is_popup = is_popup; }
|
||||
|
||||
private:
|
||||
explicit BrowsingContext(JS::NonnullGCPtr<Page>);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
JS::NonnullGCPtr<Page> m_page;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#browsing-context
|
||||
JS::GCPtr<HTML::WindowProxy> m_window_proxy;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#opener-browsing-context
|
||||
JS::GCPtr<BrowsingContext> m_opener_browsing_context;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#opener-origin-at-creation
|
||||
Optional<URL::Origin> m_opener_origin_at_creation;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#is-popup
|
||||
TokenizedFeature::Popup m_is_popup { TokenizedFeature::Popup::No };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#is-auxiliary
|
||||
bool m_is_auxiliary { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#browsing-context-initial-url
|
||||
Optional<URL::URL> m_initial_url;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#virtual-browsing-context-group-id
|
||||
u64 m_virtual_browsing_context_group_id = { 0 };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#tlbc-group
|
||||
JS::GCPtr<BrowsingContextGroup> m_group;
|
||||
|
||||
JS::GCPtr<BrowsingContext> m_first_child;
|
||||
JS::GCPtr<BrowsingContext> m_last_child;
|
||||
JS::GCPtr<BrowsingContext> m_next_sibling;
|
||||
JS::GCPtr<BrowsingContext> m_previous_sibling;
|
||||
};
|
||||
|
||||
URL::Origin determine_the_origin(Optional<URL::URL> const&, SandboxingFlagSet, Optional<URL::Origin> source_origin);
|
||||
|
||||
SandboxingFlagSet determine_the_creation_sandboxing_flags(BrowsingContext const&, JS::GCPtr<DOM::Element> embedder);
|
||||
|
||||
// FIXME: Find a better home for these
|
||||
bool url_matches_about_blank(URL::URL const& url);
|
||||
bool url_matches_about_srcdoc(URL::URL const& url);
|
||||
|
||||
}
|
70
Libraries/LibWeb/HTML/BrowsingContextGroup.cpp
Normal file
70
Libraries/LibWeb/HTML/BrowsingContextGroup.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/BrowsingContextGroup.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(BrowsingContextGroup);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-group-set
|
||||
static HashTable<JS::NonnullGCPtr<BrowsingContextGroup>>& user_agent_browsing_context_group_set()
|
||||
{
|
||||
static HashTable<JS::NonnullGCPtr<BrowsingContextGroup>> set;
|
||||
return set;
|
||||
}
|
||||
|
||||
BrowsingContextGroup::BrowsingContextGroup(JS::NonnullGCPtr<Web::Page> page)
|
||||
: m_page(page)
|
||||
{
|
||||
user_agent_browsing_context_group_set().set(*this);
|
||||
}
|
||||
|
||||
BrowsingContextGroup::~BrowsingContextGroup()
|
||||
{
|
||||
user_agent_browsing_context_group_set().remove(*this);
|
||||
}
|
||||
|
||||
void BrowsingContextGroup::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_page);
|
||||
visitor.visit(m_browsing_context_set);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#creating-a-new-browsing-context-group-and-document
|
||||
auto BrowsingContextGroup::create_a_new_browsing_context_group_and_document(JS::NonnullGCPtr<Page> page) -> WebIDL::ExceptionOr<BrowsingContextGroupAndDocument>
|
||||
{
|
||||
// 1. Let group be a new browsing context group.
|
||||
// 2. Append group to the user agent's browsing context group set.
|
||||
auto group = Bindings::main_thread_vm().heap().allocate_without_realm<BrowsingContextGroup>(page);
|
||||
|
||||
// 3. Let browsingContext and document be the result of creating a new browsing context and document with null, null, and group.
|
||||
auto [browsing_context, document] = TRY(BrowsingContext::create_a_new_browsing_context_and_document(page, nullptr, nullptr, group));
|
||||
|
||||
// 4. Append browsingContext to group.
|
||||
group->append(browsing_context);
|
||||
|
||||
// 5. Return group and document.
|
||||
return BrowsingContextGroupAndDocument { group, document };
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#bcg-append
|
||||
void BrowsingContextGroup::append(BrowsingContext& browsing_context)
|
||||
{
|
||||
VERIFY(browsing_context.is_top_level());
|
||||
|
||||
// 1. Append browsingContext to group's browsing context set.
|
||||
m_browsing_context_set.set(browsing_context);
|
||||
|
||||
// 2. Set browsingContext's group to group.
|
||||
browsing_context.set_group(this);
|
||||
}
|
||||
|
||||
}
|
49
Libraries/LibWeb/HTML/BrowsingContextGroup.h
Normal file
49
Libraries/LibWeb/HTML/BrowsingContextGroup.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class BrowsingContextGroup final : public JS::Cell {
|
||||
JS_CELL(BrowsingContextGroup, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(BrowsingContextGroup);
|
||||
|
||||
public:
|
||||
struct BrowsingContextGroupAndDocument {
|
||||
JS::NonnullGCPtr<HTML::BrowsingContextGroup> browsing_context;
|
||||
JS::NonnullGCPtr<DOM::Document> document;
|
||||
};
|
||||
static WebIDL::ExceptionOr<BrowsingContextGroupAndDocument> create_a_new_browsing_context_group_and_document(JS::NonnullGCPtr<Page>);
|
||||
|
||||
~BrowsingContextGroup();
|
||||
|
||||
Page& page() { return m_page; }
|
||||
Page const& page() const { return m_page; }
|
||||
|
||||
auto& browsing_context_set() { return m_browsing_context_set; }
|
||||
auto const& browsing_context_set() const { return m_browsing_context_set; }
|
||||
|
||||
void append(BrowsingContext&);
|
||||
|
||||
private:
|
||||
explicit BrowsingContextGroup(JS::NonnullGCPtr<Web::Page>);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-group-set
|
||||
OrderedHashTable<JS::NonnullGCPtr<BrowsingContext>> m_browsing_context_set;
|
||||
|
||||
JS::NonnullGCPtr<Page> m_page;
|
||||
};
|
||||
|
||||
}
|
46
Libraries/LibWeb/HTML/CORSSettingAttribute.cpp
Normal file
46
Libraries/LibWeb/HTML/CORSSettingAttribute.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/CORSSettingAttribute.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
|
||||
CORSSettingAttribute cors_setting_attribute_from_keyword(Optional<String> const& keyword)
|
||||
{
|
||||
if (!keyword.has_value()) {
|
||||
// its missing value default is the No CORS state
|
||||
return CORSSettingAttribute::NoCORS;
|
||||
}
|
||||
if (keyword->bytes_as_string_view().equals_ignoring_ascii_case("use-credentials"sv)) {
|
||||
return CORSSettingAttribute::UseCredentials;
|
||||
}
|
||||
|
||||
// The attribute's invalid value default is the Anonymous state
|
||||
return CORSSettingAttribute::Anonymous;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute-credentials-mode
|
||||
Fetch::Infrastructure::Request::CredentialsMode cors_settings_attribute_credentials_mode(CORSSettingAttribute attribute)
|
||||
{
|
||||
switch (attribute) {
|
||||
// -> No CORS
|
||||
// -> Anonymous
|
||||
case CORSSettingAttribute::NoCORS:
|
||||
case CORSSettingAttribute::Anonymous:
|
||||
// "same-origin"
|
||||
return Fetch::Infrastructure::Request::CredentialsMode::SameOrigin;
|
||||
|
||||
// -> Use Credentials
|
||||
case CORSSettingAttribute::UseCredentials:
|
||||
// "include"
|
||||
return Fetch::Infrastructure::Request::CredentialsMode::Include;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
25
Libraries/LibWeb/HTML/CORSSettingAttribute.h
Normal file
25
Libraries/LibWeb/HTML/CORSSettingAttribute.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute
|
||||
enum class CORSSettingAttribute {
|
||||
NoCORS,
|
||||
Anonymous,
|
||||
UseCredentials
|
||||
};
|
||||
|
||||
[[nodiscard]] CORSSettingAttribute cors_setting_attribute_from_keyword(Optional<String> const& keyword);
|
||||
[[nodiscard]] Fetch::Infrastructure::Request::CredentialsMode cors_settings_attribute_credentials_mode(CORSSettingAttribute);
|
||||
|
||||
}
|
25
Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h
Normal file
25
Libraries/LibWeb/HTML/Canvas/CanvasCompositing.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/ImageData.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvascompositing
|
||||
class CanvasCompositing {
|
||||
public:
|
||||
virtual ~CanvasCompositing() = default;
|
||||
|
||||
virtual float global_alpha() const = 0;
|
||||
virtual void set_global_alpha(float) = 0;
|
||||
|
||||
protected:
|
||||
CanvasCompositing() = default;
|
||||
};
|
||||
|
||||
}
|
6
Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl
Normal file
6
Libraries/LibWeb/HTML/Canvas/CanvasCompositing.idl
Normal file
|
@ -0,0 +1,6 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvascompositing
|
||||
interface mixin CanvasCompositing {
|
||||
// compositing
|
||||
attribute unrestricted double globalAlpha; // (default 1.0)
|
||||
[FIXME] attribute DOMString globalCompositeOperation; // (default "source-over")
|
||||
};
|
92
Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.cpp
Normal file
92
Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
|
||||
#include <LibWeb/HTML/ImageBitmap.h>
|
||||
#include <LibWeb/SVG/SVGImageElement.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
static void default_source_size(CanvasImageSource const& image, float& source_width, float& source_height)
|
||||
{
|
||||
image.visit(
|
||||
[&source_width, &source_height](JS::Handle<HTMLImageElement> const& source) {
|
||||
if (source->immutable_bitmap()) {
|
||||
source_width = source->immutable_bitmap()->width();
|
||||
source_height = source->immutable_bitmap()->height();
|
||||
} else {
|
||||
// FIXME: This is very janky and not correct.
|
||||
source_width = source->width();
|
||||
source_height = source->height();
|
||||
}
|
||||
},
|
||||
[&source_width, &source_height](JS::Handle<SVG::SVGImageElement> const& source) {
|
||||
if (source->current_image_bitmap()) {
|
||||
source_width = source->current_image_bitmap()->width();
|
||||
source_height = source->current_image_bitmap()->height();
|
||||
} else {
|
||||
// FIXME: This is very janky and not correct.
|
||||
source_width = source->width()->anim_val()->value();
|
||||
source_height = source->height()->anim_val()->value();
|
||||
}
|
||||
},
|
||||
[&source_width, &source_height](JS::Handle<HTML::HTMLVideoElement> const& source) {
|
||||
if (auto const bitmap = source->bitmap(); bitmap) {
|
||||
source_width = bitmap->width();
|
||||
source_height = bitmap->height();
|
||||
} else {
|
||||
source_width = source->video_width();
|
||||
source_height = source->video_height();
|
||||
}
|
||||
},
|
||||
[&source_width, &source_height](JS::Handle<HTMLCanvasElement> const& source) {
|
||||
if (source->surface()) {
|
||||
source_width = source->surface()->size().width();
|
||||
source_height = source->surface()->size().height();
|
||||
} else {
|
||||
source_width = source->width();
|
||||
source_height = source->height();
|
||||
}
|
||||
},
|
||||
[&source_width, &source_height](auto const& source) {
|
||||
if (source->bitmap()) {
|
||||
source_width = source->bitmap()->width();
|
||||
source_height = source->bitmap()->height();
|
||||
} else {
|
||||
source_width = source->width();
|
||||
source_height = source->height();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float destination_x, float destination_y)
|
||||
{
|
||||
// If not specified, the dw and dh arguments must default to the values of sw and sh, interpreted such that one CSS pixel in the image is treated as one unit in the output bitmap's coordinate space.
|
||||
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
|
||||
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
|
||||
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
|
||||
float source_width;
|
||||
float source_height;
|
||||
default_source_size(image, source_width, source_height);
|
||||
return draw_image_internal(image, 0, 0, source_width, source_height, destination_x, destination_y, source_width, source_height);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float destination_x, float destination_y, float destination_width, float destination_height)
|
||||
{
|
||||
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
|
||||
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
|
||||
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
|
||||
float source_width;
|
||||
float source_height;
|
||||
default_source_size(image, source_width, source_height);
|
||||
return draw_image_internal(image, 0, 0, source_width, source_height, destination_x, destination_y, destination_width, destination_height);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height)
|
||||
{
|
||||
return draw_image_internal(image, source_x, source_y, source_width, source_height, destination_x, destination_y, destination_width, destination_height);
|
||||
}
|
||||
}
|
36
Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.h
Normal file
36
Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/HTMLCanvasElement.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/HTMLVideoElement.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagesource
|
||||
// NOTE: This is the Variant created by the IDL wrapper generator, and needs to be updated accordingly.
|
||||
using CanvasImageSource = Variant<JS::Handle<HTMLImageElement>, JS::Handle<SVG::SVGImageElement>, JS::Handle<HTMLCanvasElement>, JS::Handle<ImageBitmap>, JS::Handle<HTMLVideoElement>>;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawimage
|
||||
class CanvasDrawImage {
|
||||
public:
|
||||
virtual ~CanvasDrawImage() = default;
|
||||
|
||||
WebIDL::ExceptionOr<void> draw_image(CanvasImageSource const&, float destination_x, float destination_y);
|
||||
WebIDL::ExceptionOr<void> draw_image(CanvasImageSource const&, float destination_x, float destination_y, float destination_width, float destination_height);
|
||||
WebIDL::ExceptionOr<void> draw_image(CanvasImageSource const&, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height);
|
||||
|
||||
virtual WebIDL::ExceptionOr<void> draw_image_internal(CanvasImageSource const&, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height) = 0;
|
||||
|
||||
protected:
|
||||
CanvasDrawImage() = default;
|
||||
};
|
||||
|
||||
}
|
22
Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.idl
Normal file
22
Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.idl
Normal file
|
@ -0,0 +1,22 @@
|
|||
#import <HTML/HTMLCanvasElement.idl>
|
||||
#import <HTML/HTMLImageElement.idl>
|
||||
#import <HTML/HTMLVideoElement.idl>
|
||||
#import <HTML/ImageBitmap.idl>
|
||||
#import <SVG/SVGImageElement.idl>
|
||||
|
||||
typedef (HTMLImageElement or
|
||||
SVGImageElement or
|
||||
// FIXME: We should use HTMLOrSVGImageElement instead of HTMLImageElement
|
||||
HTMLVideoElement or
|
||||
HTMLCanvasElement or
|
||||
ImageBitmap
|
||||
// FIXME: OffscreenCanvas
|
||||
// FIXME: VideoFrame
|
||||
) CanvasImageSource;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawimage
|
||||
interface mixin CanvasDrawImage {
|
||||
undefined drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy);
|
||||
undefined drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
|
||||
undefined drawImage(CanvasImageSource image, unrestricted double sx, unrestricted double sy, unrestricted double sw, unrestricted double sh, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
|
||||
};
|
37
Libraries/LibWeb/HTML/Canvas/CanvasDrawPath.h
Normal file
37
Libraries/LibWeb/HTML/Canvas/CanvasDrawPath.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <LibWeb/HTML/Path2D.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawpath
|
||||
class CanvasDrawPath {
|
||||
public:
|
||||
virtual ~CanvasDrawPath() = default;
|
||||
|
||||
virtual void begin_path() = 0;
|
||||
|
||||
virtual void fill(StringView fill_rule) = 0;
|
||||
virtual void fill(Path2D& path, StringView fill_rule) = 0;
|
||||
|
||||
virtual void stroke() = 0;
|
||||
virtual void stroke(Path2D const& path) = 0;
|
||||
|
||||
virtual void clip(StringView fill_rule) = 0;
|
||||
virtual void clip(Path2D& path, StringView fill_rule) = 0;
|
||||
|
||||
virtual bool is_point_in_path(double x, double y, StringView fill_rule) = 0;
|
||||
virtual bool is_point_in_path(Path2D const& path, double x, double y, StringView fill_rule) = 0;
|
||||
|
||||
protected:
|
||||
CanvasDrawPath() = default;
|
||||
};
|
||||
|
||||
}
|
27
Libraries/LibWeb/HTML/Canvas/CanvasDrawPath.idl
Normal file
27
Libraries/LibWeb/HTML/Canvas/CanvasDrawPath.idl
Normal file
|
@ -0,0 +1,27 @@
|
|||
#import <HTML/Path2D.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillrule
|
||||
enum CanvasFillRule { "nonzero", "evenodd" };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawpath
|
||||
interface mixin CanvasDrawPath {
|
||||
undefined beginPath();
|
||||
// FIXME: `DOMString` should be `CanvasFillRule`
|
||||
undefined fill(optional DOMString fillRule = "nonzero");
|
||||
// FIXME: `DOMString` should be `CanvasFillRule`
|
||||
undefined fill(Path2D path, optional DOMString fillRule = "nonzero");
|
||||
undefined stroke();
|
||||
undefined stroke(Path2D path);
|
||||
|
||||
// FIXME: `DOMString` should be `CanvasFillRule`
|
||||
undefined clip(optional DOMString fillRule = "nonzero");
|
||||
// FIXME: `DOMString` should be `CanvasFillRule`
|
||||
undefined clip(Path2D path, optional DOMString fillRule = "nonzero");
|
||||
|
||||
// FIXME: `DOMString` should be `CanvasFillRule`
|
||||
boolean isPointInPath(unrestricted double x, unrestricted double y, optional DOMString fillRule = "nonzero");
|
||||
// FIXME: `DOMString` should be `CanvasFillRule`
|
||||
boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, optional DOMString fillRule = "nonzero");
|
||||
[FIXME] boolean isPointInStroke(unrestricted double x, unrestricted double y);
|
||||
[FIXME] boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
|
||||
};
|
142
Libraries/LibWeb/HTML/Canvas/CanvasFillStrokeStyles.h
Normal file
142
Libraries/LibWeb/HTML/Canvas/CanvasFillStrokeStyles.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles
|
||||
template<typename IncludingClass>
|
||||
class CanvasFillStrokeStyles {
|
||||
public:
|
||||
~CanvasFillStrokeStyles() = default;
|
||||
using FillOrStrokeStyleVariant = Variant<String, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
|
||||
|
||||
void set_fill_style(FillOrStrokeStyleVariant style)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
|
||||
style.visit(
|
||||
// 1. If the given value is a string, then:
|
||||
[&](String const& string) {
|
||||
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
|
||||
auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), string);
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
|
||||
// FIXME: Parse a color value
|
||||
// https://drafts.csswg.org/css-color/#parse-a-css-color-value
|
||||
auto style_value = parser.parse_as_css_value(CSS::PropertyID::Color);
|
||||
if (style_value && style_value->has_color()) {
|
||||
auto parsedValue = style_value->to_color(OptionalNone());
|
||||
|
||||
// 4. Set this's fill style to parsedValue.
|
||||
my_drawing_state().fill_style = parsedValue;
|
||||
} else {
|
||||
// 3. If parsedValue is failure, then return.
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Return.
|
||||
return;
|
||||
},
|
||||
[&](auto fill_or_stroke_style) {
|
||||
// FIXME: 2. If the given value is a CanvasPattern object that is marked as not origin-clean, then set this's origin-clean flag to false.
|
||||
|
||||
// 3. Set this's fill style to the given value.
|
||||
my_drawing_state().fill_style = fill_or_stroke_style;
|
||||
});
|
||||
}
|
||||
|
||||
FillOrStrokeStyleVariant fill_style() const
|
||||
{
|
||||
return my_drawing_state().fill_style.to_js_fill_or_stroke_style();
|
||||
}
|
||||
|
||||
void set_stroke_style(FillOrStrokeStyleVariant style)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-strokestyle
|
||||
|
||||
style.visit(
|
||||
// 1. If the given value is a string, then:
|
||||
[&](String const& string) {
|
||||
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
|
||||
auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), string);
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
|
||||
// FIXME: Parse a color value
|
||||
// https://drafts.csswg.org/css-color/#parse-a-css-color-value
|
||||
auto style_value = parser.parse_as_css_value(CSS::PropertyID::Color);
|
||||
if (style_value && style_value->has_color()) {
|
||||
auto parsedValue = style_value->to_color(OptionalNone());
|
||||
|
||||
// 4. Set this's stroke style to parsedValue.
|
||||
my_drawing_state().stroke_style = parsedValue;
|
||||
} else {
|
||||
// 3. If parsedValue is failure, then return.
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Return.
|
||||
return;
|
||||
},
|
||||
[&](auto fill_or_stroke_style) {
|
||||
// FIXME: 2. If the given value is a CanvasPattern object that is marked as not origin-clean, then set this's origin-clean flag to false.
|
||||
|
||||
// 3. Set this's stroke style to the given value.
|
||||
my_drawing_state().fill_style = fill_or_stroke_style;
|
||||
});
|
||||
}
|
||||
|
||||
FillOrStrokeStyleVariant stroke_style() const
|
||||
{
|
||||
return my_drawing_state().stroke_style.to_js_fill_or_stroke_style();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> create_radial_gradient(double x0, double y0, double r0, double x1, double y1, double r1)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
return CanvasGradient::create_radial(realm, x0, y0, r0, x1, y1, r1);
|
||||
}
|
||||
|
||||
JS::NonnullGCPtr<CanvasGradient> create_linear_gradient(double x0, double y0, double x1, double y1)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
return CanvasGradient::create_linear(realm, x0, y0, x1, y1).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
JS::NonnullGCPtr<CanvasGradient> create_conic_gradient(double start_angle, double x, double y)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
return CanvasGradient::create_conic(realm, start_angle, x, y).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create_pattern(CanvasImageSource const& image, StringView repetition)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
return CanvasPattern::create(realm, image, repetition);
|
||||
}
|
||||
|
||||
protected:
|
||||
CanvasFillStrokeStyles() = default;
|
||||
|
||||
private:
|
||||
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
|
||||
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
|
||||
};
|
||||
|
||||
}
|
14
Libraries/LibWeb/HTML/Canvas/CanvasFillStrokeStyles.idl
Normal file
14
Libraries/LibWeb/HTML/Canvas/CanvasFillStrokeStyles.idl
Normal file
|
@ -0,0 +1,14 @@
|
|||
#import <HTML/CanvasGradient.idl>
|
||||
#import <HTML/CanvasPattern.idl>
|
||||
#import <HTML/HTMLCanvasElement.idl>
|
||||
#import <HTML/HTMLImageElement.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles
|
||||
interface mixin CanvasFillStrokeStyles {
|
||||
attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle;
|
||||
attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle;
|
||||
CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
|
||||
CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
|
||||
CanvasGradient createConicGradient(double startAngle, double x, double y);
|
||||
CanvasPattern? createPattern(CanvasImageSource image, [LegacyNullToEmptyString] DOMString repetition);
|
||||
};
|
5
Libraries/LibWeb/HTML/Canvas/CanvasFilters.idl
Normal file
5
Libraries/LibWeb/HTML/Canvas/CanvasFilters.idl
Normal file
|
@ -0,0 +1,5 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfilters
|
||||
interface mixin CanvasFilters {
|
||||
// filters
|
||||
[FIXME] attribute DOMString filter; // (default "none")
|
||||
};
|
27
Libraries/LibWeb/HTML/Canvas/CanvasImageData.h
Normal file
27
Libraries/LibWeb/HTML/Canvas/CanvasImageData.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/ImageData.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagedata
|
||||
class CanvasImageData {
|
||||
public:
|
||||
virtual ~CanvasImageData() = default;
|
||||
|
||||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> create_image_data(int width, int height, Optional<ImageDataSettings> const& settings = {}) const = 0;
|
||||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> create_image_data(ImageData const&) const = 0;
|
||||
virtual WebIDL::ExceptionOr<JS::GCPtr<ImageData>> get_image_data(int x, int y, int width, int height, Optional<ImageDataSettings> const& settings = {}) const = 0;
|
||||
virtual void put_image_data(ImageData const&, float x, float y) = 0;
|
||||
|
||||
protected:
|
||||
CanvasImageData() = default;
|
||||
};
|
||||
|
||||
}
|
12
Libraries/LibWeb/HTML/Canvas/CanvasImageData.idl
Normal file
12
Libraries/LibWeb/HTML/Canvas/CanvasImageData.idl
Normal file
|
@ -0,0 +1,12 @@
|
|||
#import <HTML/ImageData.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagedata
|
||||
interface mixin CanvasImageData {
|
||||
ImageData createImageData([EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {});
|
||||
ImageData createImageData(ImageData imagedata);
|
||||
|
||||
ImageData getImageData([EnforceRange] long sx, [EnforceRange] long sy, [EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {});
|
||||
|
||||
undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy);
|
||||
[FIXME] undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight);
|
||||
};
|
27
Libraries/LibWeb/HTML/Canvas/CanvasImageSmoothing.h
Normal file
27
Libraries/LibWeb/HTML/Canvas/CanvasImageSmoothing.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/ImageData.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagesmoothing
|
||||
class CanvasImageSmoothing {
|
||||
public:
|
||||
virtual ~CanvasImageSmoothing() = default;
|
||||
|
||||
virtual bool image_smoothing_enabled() const = 0;
|
||||
virtual void set_image_smoothing_enabled(bool) = 0;
|
||||
virtual Bindings::ImageSmoothingQuality image_smoothing_quality() const = 0;
|
||||
virtual void set_image_smoothing_quality(Bindings::ImageSmoothingQuality) = 0;
|
||||
|
||||
protected:
|
||||
CanvasImageSmoothing() = default;
|
||||
};
|
||||
|
||||
}
|
8
Libraries/LibWeb/HTML/Canvas/CanvasImageSmoothing.idl
Normal file
8
Libraries/LibWeb/HTML/Canvas/CanvasImageSmoothing.idl
Normal file
|
@ -0,0 +1,8 @@
|
|||
#import <HTML/CanvasRenderingContext2D.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagesmoothing
|
||||
interface mixin CanvasImageSmoothing {
|
||||
// image smoothing
|
||||
attribute boolean imageSmoothingEnabled; // (default true)
|
||||
attribute ImageSmoothingQuality imageSmoothingQuality; // (default low)
|
||||
};
|
441
Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp
Normal file
441
Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp
Normal file
|
@ -0,0 +1,441 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Vector2.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasPath.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
Gfx::AffineTransform CanvasPath::active_transform() const
|
||||
{
|
||||
if (m_canvas_state.has_value())
|
||||
return m_canvas_state->drawing_state().transform;
|
||||
return {};
|
||||
}
|
||||
|
||||
void CanvasPath::ensure_subpath(float x, float y)
|
||||
{
|
||||
if (m_path.is_empty())
|
||||
m_path.move_to(Gfx::FloatPoint { x, y });
|
||||
}
|
||||
|
||||
void CanvasPath::close_path()
|
||||
{
|
||||
m_path.close();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-moveto
|
||||
void CanvasPath::move_to(float x, float y)
|
||||
{
|
||||
// 1. If either of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(x) || !isfinite(y))
|
||||
return;
|
||||
|
||||
// 2. Create a new subpath with the specified point as its first (and only) point.
|
||||
m_path.move_to(Gfx::FloatPoint { x, y });
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-lineto
|
||||
void CanvasPath::line_to(float x, float y)
|
||||
{
|
||||
// 1. If either of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(x) || !isfinite(y))
|
||||
return;
|
||||
|
||||
if (m_path.is_empty()) {
|
||||
// 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
|
||||
ensure_subpath(x, y);
|
||||
} else {
|
||||
// 3. Otherwise, connect the last point in the subpath to the given point (x, y) using a straight line,
|
||||
// and then add the given point (x, y) to the subpath.
|
||||
m_path.line_to(Gfx::FloatPoint { x, y });
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-quadraticcurveto
|
||||
void CanvasPath::quadratic_curve_to(float cpx, float cpy, float x, float y)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(cpx) || !isfinite(cpy) || !isfinite(x) || !isfinite(y))
|
||||
return;
|
||||
|
||||
// 2. Ensure there is a subpath for (cpx, cpy)
|
||||
ensure_subpath(cpx, cpy);
|
||||
|
||||
// 3. Connect the last point in the subpath to the given point (x, y) using a quadratic Bézier curve with control point (cpx, cpy).
|
||||
// 4. Add the given point (x, y) to the subpath.
|
||||
m_path.quadratic_bezier_curve_to(Gfx::FloatPoint { cpx, cpy }, Gfx::FloatPoint { x, y });
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-beziercurveto
|
||||
void CanvasPath::bezier_curve_to(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(cp1x) || !isfinite(cp1y) || !isfinite(cp2x) || !isfinite(cp2y) || !isfinite(x) || !isfinite(y))
|
||||
return;
|
||||
|
||||
// 2. Ensure there is a subpath for (cp1x, cp1y)
|
||||
ensure_subpath(cp1x, cp1y);
|
||||
|
||||
// 3. Connect the last point in the subpath to the given point (x, y) using a cubic Bézier curve with control poits (cp1x, cp1y) and (cp2x, cp2y).
|
||||
// 4. Add the point (x, y) to the subpath.
|
||||
m_path.cubic_bezier_curve_to(
|
||||
Gfx::FloatPoint { cp1x, cp1y }, Gfx::FloatPoint { cp2x, cp2y }, Gfx::FloatPoint { x, y });
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> CanvasPath::arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise)
|
||||
{
|
||||
if (radius < 0)
|
||||
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The radius provided ({}) is negative.", radius)));
|
||||
return ellipse(x, y, radius, radius, 0, start_angle, end_angle, counter_clockwise);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-ellipse
|
||||
WebIDL::ExceptionOr<void> CanvasPath::ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(x) || !isfinite(y) || !isfinite(radius_x) || !isfinite(radius_y) || !isfinite(rotation) || !isfinite(start_angle) || !isfinite(end_angle))
|
||||
return {};
|
||||
|
||||
// 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
|
||||
if (radius_x < 0)
|
||||
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The major-axis radius provided ({}) is negative.", radius_x)));
|
||||
if (radius_y < 0)
|
||||
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The minor-axis radius provided ({}) is negative.", radius_y)));
|
||||
|
||||
// "If counterclockwise is false and endAngle − startAngle is greater than or equal to 2π,
|
||||
// or, if counterclockwise is true and startAngle − endAngle is greater than or equal to 2π,
|
||||
// then the arc is the whole circumference of this ellipse"
|
||||
// Also draw the full ellipse if making a non-zero whole number of turns.
|
||||
if (constexpr float tau = M_PI * 2; (!counter_clockwise && (end_angle - start_angle) >= tau)
|
||||
|| (counter_clockwise && (start_angle - end_angle) >= tau)
|
||||
|| (start_angle != end_angle && fmodf(start_angle - end_angle, tau) == 0)) {
|
||||
start_angle = 0;
|
||||
// FIXME: elliptical_arc_to() incorrectly handles the case where the start/end points are very close.
|
||||
// So we slightly fudge the numbers here to correct for that.
|
||||
end_angle = tau * 0.9999f;
|
||||
counter_clockwise = false;
|
||||
} else {
|
||||
start_angle = fmodf(start_angle, tau);
|
||||
end_angle = fmodf(end_angle, tau);
|
||||
}
|
||||
|
||||
// Then, figure out where the ends of the arc are.
|
||||
// To do so, we can pretend that the center of this ellipse is at (0, 0),
|
||||
// and the whole coordinate system is rotated `rotation` radians around the x axis, centered on `center`.
|
||||
// The sign of the resulting relative positions is just whether our angle is on one of the left quadrants.
|
||||
float sin_rotation;
|
||||
float cos_rotation;
|
||||
AK::sincos(rotation, sin_rotation, cos_rotation);
|
||||
|
||||
auto resolve_point_with_angle = [&](float angle) {
|
||||
auto tan_relative = tanf(angle);
|
||||
auto tan2 = tan_relative * tan_relative;
|
||||
|
||||
auto ab = radius_x * radius_y;
|
||||
auto a2 = radius_x * radius_x;
|
||||
auto b2 = radius_y * radius_y;
|
||||
auto sqrt = sqrtf(b2 + a2 * tan2);
|
||||
|
||||
auto relative_x_position = ab / sqrt;
|
||||
auto relative_y_position = ab * tan_relative / sqrt;
|
||||
|
||||
// Make sure to set the correct sign
|
||||
// -1 if 0 ≤ θ < 90° or 270°< θ ≤ 360°
|
||||
// 1 if 90° < θ< 270°
|
||||
float sn = cosf(angle) >= 0 ? 1 : -1;
|
||||
relative_x_position *= sn;
|
||||
relative_y_position *= sn;
|
||||
|
||||
// Now rotate it (back) around the center point by 'rotation' radians, then move it back to our actual origin.
|
||||
auto relative_rotated_x_position = relative_x_position * cos_rotation - relative_y_position * sin_rotation;
|
||||
auto relative_rotated_y_position = relative_x_position * sin_rotation + relative_y_position * cos_rotation;
|
||||
return Gfx::FloatPoint { relative_rotated_x_position + x, relative_rotated_y_position + y };
|
||||
};
|
||||
|
||||
auto start_point = resolve_point_with_angle(start_angle);
|
||||
auto end_point = resolve_point_with_angle(end_angle);
|
||||
|
||||
float delta_theta;
|
||||
if (counter_clockwise) {
|
||||
delta_theta = start_angle - end_angle;
|
||||
} else {
|
||||
delta_theta = end_angle - start_angle;
|
||||
}
|
||||
|
||||
if (delta_theta < 0)
|
||||
delta_theta += AK::Pi<float> * 2;
|
||||
|
||||
// 3. If canvasPath's path has any subpaths, then add a straight line from the last point in the subpath to the start point of the arc.
|
||||
if (!m_path.is_empty())
|
||||
m_path.line_to(start_point);
|
||||
else
|
||||
m_path.move_to(start_point);
|
||||
|
||||
// 4. Add the start and end points of the arc to the subpath, and connect them with an arc.
|
||||
m_path.elliptical_arc_to(
|
||||
Gfx::FloatPoint { end_point },
|
||||
Gfx::FloatSize { radius_x, radius_y },
|
||||
rotation,
|
||||
delta_theta > AK::Pi<float>, !counter_clockwise);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arcto
|
||||
WebIDL::ExceptionOr<void> CanvasPath::arc_to(double x1, double y1, double x2, double y2, double radius)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(x1) || !isfinite(y1) || !isfinite(x2) || !isfinite(y2) || !isfinite(radius))
|
||||
return {};
|
||||
|
||||
// 2. Ensure there is a subpath for (x1, y1).
|
||||
ensure_subpath(x1, y1);
|
||||
|
||||
// 3. If radius is negative, then throw an "IndexSizeError" DOMException.
|
||||
if (radius < 0)
|
||||
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The radius provided ({}) is negative.", radius)));
|
||||
|
||||
auto transform = active_transform();
|
||||
|
||||
// 4. Let the point (x0, y0) be the last point in the subpath,
|
||||
// transformed by the inverse of the current transformation matrix
|
||||
// (so that it is in the same coordinate system as the points passed to the method).
|
||||
// Point (x0, y0)
|
||||
auto p0 = transform.inverse().value_or(Gfx::AffineTransform()).map(m_path.last_point());
|
||||
// Point (x1, y1)
|
||||
auto p1 = Gfx::FloatPoint { x1, y1 };
|
||||
// Point (x2, y2)
|
||||
auto p2 = Gfx::FloatPoint { x2, y2 };
|
||||
|
||||
// 5. If the point (x0, y0) is equal to the point (x1, y1),
|
||||
// or if the point (x1, y1) is equal to the point (x2, y2),
|
||||
// or if radius is zero, then add the point (x1, y1) to the subpath,
|
||||
// and connect that point to the previous point (x0, y0) by a straight line.
|
||||
if (p0 == p1 || p1 == p2 || radius == 0) {
|
||||
m_path.line_to(p1);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto v1 = Gfx::FloatVector2 { p0.x() - p1.x(), p0.y() - p1.y() };
|
||||
auto v2 = Gfx::FloatVector2 { p2.x() - p1.x(), p2.y() - p1.y() };
|
||||
auto cos_theta = v1.dot(v2) / (v1.length() * v2.length());
|
||||
// 6. Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line,
|
||||
// then add the point (x1, y1) to the subpath,
|
||||
// and connect that point to the previous point (x0, y0) by a straight line.
|
||||
if (-1 == cos_theta || 1 == cos_theta) {
|
||||
m_path.line_to(p1);
|
||||
return {};
|
||||
}
|
||||
|
||||
// 7. Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius,
|
||||
// and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1),
|
||||
// and that has a different point tangent to the half-infinite line that ends at the point (x1, y1) and crosses the point (x2, y2).
|
||||
// The points at which this circle touches these two lines are called the start and end tangent points respectively.
|
||||
auto adjacent = radius / static_cast<double>(tan(acos(cos_theta) / 2));
|
||||
auto factor1 = adjacent / static_cast<double>(v1.length());
|
||||
auto x3 = static_cast<double>(p1.x()) + factor1 * static_cast<double>(p0.x() - p1.x());
|
||||
auto y3 = static_cast<double>(p1.y()) + factor1 * static_cast<double>(p0.y() - p1.y());
|
||||
auto start_tangent = Gfx::FloatPoint { x3, y3 };
|
||||
|
||||
auto factor2 = adjacent / static_cast<double>(v2.length());
|
||||
auto x4 = static_cast<double>(p1.x()) + factor2 * static_cast<double>(p2.x() - p1.x());
|
||||
auto y4 = static_cast<double>(p1.y()) + factor2 * static_cast<double>(p2.y() - p1.y());
|
||||
auto end_tangent = Gfx::FloatPoint { x4, y4 };
|
||||
|
||||
// Connect the point (x0, y0) to the start tangent point by a straight line, adding the start tangent point to the subpath.
|
||||
m_path.line_to(start_tangent);
|
||||
|
||||
bool const large_arc = false; // always small since tangent points define arc endpoints and lines meet at (x1, y1)
|
||||
auto cross_product = v1.x() * v2.y() - v1.y() * v2.x();
|
||||
bool const sweep = cross_product < 0; // right-hand rule, true means clockwise
|
||||
|
||||
// and then connect the start tangent point to the end tangent point by The Arc, adding the end tangent point to the subpath.
|
||||
m_path.arc_to(end_tangent, radius, large_arc, sweep);
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
|
||||
void CanvasPath::rect(double x, double y, double w, double h)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h))
|
||||
return;
|
||||
|
||||
// 2. Create a new subpath containing just the four points (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order, with those four points connected by straight lines.
|
||||
m_path.move_to(Gfx::FloatPoint { x, y });
|
||||
m_path.line_to(Gfx::FloatPoint { x + w, y });
|
||||
m_path.line_to(Gfx::FloatPoint { x + w, y + h });
|
||||
m_path.line_to(Gfx::FloatPoint { x, y + h });
|
||||
|
||||
// 3. Mark the subpath as closed.
|
||||
m_path.close();
|
||||
|
||||
// 4. Create a new subpath with the point (x, y) as the only point in the subpath.
|
||||
m_path.move_to(Gfx::FloatPoint { x, y });
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
|
||||
WebIDL::ExceptionOr<void> CanvasPath::round_rect(double x, double y, double w, double h, Variant<double, Geometry::DOMPointInit, Vector<Variant<double, Geometry::DOMPointInit>>> radii)
|
||||
{
|
||||
using Radius = Variant<double, Geometry::DOMPointInit>;
|
||||
|
||||
// 1. If any of x, y, w, or h are infinite or NaN, then return.
|
||||
if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h))
|
||||
return {};
|
||||
|
||||
// 2. If radii is an unrestricted double or DOMPointInit, then set radii to « radii ».
|
||||
if (radii.has<double>() || radii.has<Geometry::DOMPointInit>()) {
|
||||
Vector<Radius> radii_list;
|
||||
if (radii.has<double>())
|
||||
radii_list.append(radii.get<double>());
|
||||
else
|
||||
radii_list.append(radii.get<Geometry::DOMPointInit>());
|
||||
radii = radii_list;
|
||||
}
|
||||
|
||||
// 3. If radii is not a list of size one, two, three, or four, then throw a RangeError.
|
||||
if (radii.get<Vector<Radius>>().is_empty() || radii.get<Vector<Radius>>().size() > 4)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Can have between 1 and 4 radii"sv };
|
||||
|
||||
// 4. Let normalizedRadii be an empty list.
|
||||
Vector<Geometry::DOMPointInit> normalized_radii;
|
||||
|
||||
// 5. For each radius of radii:
|
||||
for (auto const& radius : radii.get<Vector<Radius>>()) {
|
||||
// 5.1. If radius is a DOMPointInit:
|
||||
if (radius.has<Geometry::DOMPointInit>()) {
|
||||
auto const& radius_as_dom_point = radius.get<Geometry::DOMPointInit>();
|
||||
|
||||
// 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then return.
|
||||
if (!isfinite(radius_as_dom_point.x) || !isfinite(radius_as_dom_point.y))
|
||||
return {};
|
||||
|
||||
// 5.1.2. If radius["x"] or radius["y"] is negative, then throw a RangeError.
|
||||
if (radius_as_dom_point.x < 0 || radius_as_dom_point.y < 0)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Radius can't be negative"sv };
|
||||
|
||||
// 5.1.3. Otherwise, append radius to normalizedRadii.
|
||||
normalized_radii.append(radius_as_dom_point);
|
||||
}
|
||||
|
||||
// 5.2. If radius is a unrestricted double:
|
||||
if (radius.has<double>()) {
|
||||
auto radius_as_double = radius.get<double>();
|
||||
|
||||
// 5.2.1. If radius is infinite or NaN, then return.
|
||||
if (!isfinite(radius_as_double))
|
||||
return {};
|
||||
|
||||
// 5.2.2. If radius is negative, then throw a RangeError.
|
||||
if (radius_as_double < 0)
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Radius can't be negative"sv };
|
||||
|
||||
// 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to normalizedRadii.
|
||||
normalized_radii.append(Geometry::DOMPointInit { radius_as_double, radius_as_double });
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null.
|
||||
Geometry::DOMPointInit upper_left {};
|
||||
Geometry::DOMPointInit upper_right {};
|
||||
Geometry::DOMPointInit lower_right {};
|
||||
Geometry::DOMPointInit lower_left {};
|
||||
|
||||
// 7. If normalizedRadii's size is 4, then set upperLeft to normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight to normalizedRadii[2], and set lowerLeft to normalizedRadii[3].
|
||||
if (normalized_radii.size() == 4) {
|
||||
upper_left = normalized_radii.at(0);
|
||||
upper_right = normalized_radii.at(1);
|
||||
lower_right = normalized_radii.at(2);
|
||||
lower_left = normalized_radii.at(3);
|
||||
}
|
||||
|
||||
// 8. If normalizedRadii's size is 3, then set upperLeft to normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1], and set lowerRight to normalizedRadii[2].
|
||||
if (normalized_radii.size() == 3) {
|
||||
upper_left = normalized_radii.at(0);
|
||||
upper_right = lower_left = normalized_radii.at(1);
|
||||
lower_right = normalized_radii.at(2);
|
||||
}
|
||||
|
||||
// 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight to normalizedRadii[0] and set upperRight and lowerLeft to normalizedRadii[1].
|
||||
if (normalized_radii.size() == 2) {
|
||||
upper_left = lower_right = normalized_radii.at(0);
|
||||
upper_right = lower_left = normalized_radii.at(1);
|
||||
}
|
||||
|
||||
// 10. If normalizedRadii's size is 1, then set upperLeft, upperRight, lowerRight, and lowerLeft to normalizedRadii[0].
|
||||
if (normalized_radii.size() == 1)
|
||||
upper_left = upper_right = lower_right = lower_left = normalized_radii.at(0);
|
||||
|
||||
// 11. Corner curves must not overlap. Scale all radii to prevent this:
|
||||
// 11.1. Let top be upperLeft["x"] + upperRight["x"].
|
||||
double top = upper_left.x + upper_right.x;
|
||||
|
||||
// 11.2. Let right be upperRight["y"] + lowerRight["y"].
|
||||
double right = upper_right.y + lower_right.y;
|
||||
|
||||
// 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"].
|
||||
double bottom = lower_right.x + lower_left.x;
|
||||
|
||||
// 11.4. Let left be upperLeft["y"] + lowerLeft["y"].
|
||||
double left = upper_left.y + lower_left.y;
|
||||
|
||||
// 11.5. Let scale be the minimum value of the ratios w / top, h / right, w / bottom, h / left.
|
||||
double scale = AK::min(AK::min(w / top, h / right), AK::min(w / bottom, h / left));
|
||||
|
||||
// 11.6. If scale is less than 1, then set the x and y members of upperLeft, upperRight, lowerLeft, and lowerRight to their current values multiplied by scale.
|
||||
if (scale < 1) {
|
||||
upper_left.x *= scale;
|
||||
upper_left.y *= scale;
|
||||
upper_right.x *= scale;
|
||||
upper_right.y *= scale;
|
||||
lower_left.x *= scale;
|
||||
lower_left.y *= scale;
|
||||
lower_right.x *= scale;
|
||||
lower_right.y *= scale;
|
||||
}
|
||||
|
||||
// 12. Create a new subpath:
|
||||
bool large_arc = false;
|
||||
bool sweep = true;
|
||||
|
||||
// 12.1. Move to the point (x + upperLeft["x"], y).
|
||||
m_path.move_to(Gfx::FloatPoint { x + upper_left.x, y });
|
||||
|
||||
// 12.2. Draw a straight line to the point (x + w − upperRight["x"], y).
|
||||
m_path.line_to(Gfx::FloatPoint { x + w - upper_right.x, y });
|
||||
|
||||
// 12.3. Draw an arc to the point (x + w, y + upperRight["y"]).
|
||||
m_path.elliptical_arc_to(Gfx::FloatPoint { x + w, y + upper_right.y }, { upper_right.x, upper_right.y }, 0, large_arc, sweep);
|
||||
|
||||
// 12.4. Draw a straight line to the point (x + w, y + h − lowerRight["y"]).
|
||||
m_path.line_to(Gfx::FloatPoint { x + w, y + h - lower_right.y });
|
||||
|
||||
// 12.5. Draw an arc to the point (x + w − lowerRight["x"], y + h).
|
||||
m_path.elliptical_arc_to(Gfx::FloatPoint { x + w - lower_right.x, y + h }, { lower_right.x, lower_right.y }, 0, large_arc, sweep);
|
||||
|
||||
// 12.6. Draw a straight line to the point (x + lowerLeft["x"], y + h).
|
||||
m_path.line_to(Gfx::FloatPoint { x + lower_left.x, y + h });
|
||||
|
||||
// 12.7. Draw an arc to the point (x, y + h − lowerLeft["y"]).
|
||||
m_path.elliptical_arc_to(Gfx::FloatPoint { x, y + h - lower_left.y }, { lower_left.x, lower_left.y }, 0, large_arc, sweep);
|
||||
|
||||
// 12.8. Draw a straight line to the point (x, y + upperLeft["y"]).
|
||||
m_path.line_to(Gfx::FloatPoint { x, y + upper_left.y });
|
||||
|
||||
// 12.9. Draw an arc to the point (x + upperLeft["x"], y).
|
||||
m_path.elliptical_arc_to(Gfx::FloatPoint { x + upper_left.x, y }, { upper_left.x, upper_left.y }, 0, large_arc, sweep);
|
||||
|
||||
// 13. Mark the subpath as closed.
|
||||
m_path.close();
|
||||
|
||||
// 14. Create a new subpath with the point (x, y) as the only point in the subpath.
|
||||
m_path.move_to(Gfx::FloatPoint { x, y });
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
58
Libraries/LibWeb/HTML/Canvas/CanvasPath.h
Normal file
58
Libraries/LibWeb/HTML/Canvas/CanvasPath.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/Path.h>
|
||||
#include <LibWeb/Geometry/DOMPointReadOnly.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspath
|
||||
class CanvasPath {
|
||||
public:
|
||||
~CanvasPath() = default;
|
||||
|
||||
void close_path();
|
||||
|
||||
void move_to(float x, float y);
|
||||
void line_to(float x, float y);
|
||||
void quadratic_curve_to(float cx, float cy, float x, float y);
|
||||
void bezier_curve_to(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y);
|
||||
WebIDL::ExceptionOr<void> arc_to(double x1, double y1, double x2, double y2, double radius);
|
||||
void rect(double x, double y, double w, double h);
|
||||
WebIDL::ExceptionOr<void> round_rect(double x, double y, double w, double h, Variant<double, Geometry::DOMPointInit, Vector<Variant<double, Geometry::DOMPointInit>>> radii = { 0 });
|
||||
WebIDL::ExceptionOr<void> arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise);
|
||||
WebIDL::ExceptionOr<void> ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise);
|
||||
|
||||
Gfx::Path& path() { return m_path; }
|
||||
Gfx::Path const& path() const { return m_path; }
|
||||
|
||||
protected:
|
||||
explicit CanvasPath(Bindings::PlatformObject& self)
|
||||
: m_self(self)
|
||||
{
|
||||
}
|
||||
|
||||
explicit CanvasPath(Bindings::PlatformObject& self, CanvasState const& canvas_state)
|
||||
: m_self(self)
|
||||
, m_canvas_state(canvas_state)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Gfx::AffineTransform active_transform() const;
|
||||
|
||||
void ensure_subpath(float x, float y);
|
||||
|
||||
JS::NonnullGCPtr<Bindings::PlatformObject> m_self;
|
||||
Optional<CanvasState const&> m_canvas_state;
|
||||
Gfx::Path m_path;
|
||||
};
|
||||
|
||||
}
|
15
Libraries/LibWeb/HTML/Canvas/CanvasPath.idl
Normal file
15
Libraries/LibWeb/HTML/Canvas/CanvasPath.idl
Normal file
|
@ -0,0 +1,15 @@
|
|||
#import <Geometry/DOMPointReadOnly.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspath
|
||||
interface mixin CanvasPath {
|
||||
undefined closePath();
|
||||
undefined moveTo(unrestricted double x, unrestricted double y);
|
||||
undefined lineTo(unrestricted double x, unrestricted double y);
|
||||
undefined quadraticCurveTo(unrestricted double cpx, unrestricted double cpy, unrestricted double x, unrestricted double y);
|
||||
undefined bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y);
|
||||
undefined arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius);
|
||||
undefined rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
|
||||
undefined roundRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>) radii = 0);
|
||||
undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false);
|
||||
undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false);
|
||||
};
|
123
Libraries/LibWeb/HTML/Canvas/CanvasPathDrawingStyles.h
Normal file
123
Libraries/LibWeb/HTML/Canvas/CanvasPathDrawingStyles.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspathdrawingstyles
|
||||
template<typename IncludingClass>
|
||||
class CanvasPathDrawingStyles {
|
||||
public:
|
||||
~CanvasPathDrawingStyles() = default;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linewidth
|
||||
void set_line_width(float line_width)
|
||||
{
|
||||
// On setting, zero, negative, infinite, and NaN values must be ignored, leaving the value unchanged;
|
||||
if (line_width <= 0 || !isfinite(line_width))
|
||||
return;
|
||||
// other values must change the current value to the new value.
|
||||
my_drawing_state().line_width = line_width;
|
||||
}
|
||||
float line_width() const
|
||||
{
|
||||
// On getting, it must return the current value.
|
||||
return my_drawing_state().line_width;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linecap
|
||||
void set_line_cap(Bindings::CanvasLineCap line_cap)
|
||||
{
|
||||
// On setting, the current value must be changed to the new value.
|
||||
my_drawing_state().line_cap = line_cap;
|
||||
}
|
||||
Bindings::CanvasLineCap line_cap() const
|
||||
{
|
||||
// On getting, it must return the current value.
|
||||
return my_drawing_state().line_cap;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linejoin
|
||||
void set_line_join(Bindings::CanvasLineJoin line_join)
|
||||
{
|
||||
// On setting, the current value must be changed to the new value.
|
||||
my_drawing_state().line_join = line_join;
|
||||
}
|
||||
Bindings::CanvasLineJoin line_join() const
|
||||
{
|
||||
// On getting, it must return the current value.
|
||||
return my_drawing_state().line_join;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-miterlimit
|
||||
void set_miter_limit(float miter_limit)
|
||||
{
|
||||
// On setting, zero, negative, infinite, and NaN values must be ignored, leaving the value unchanged;
|
||||
if (miter_limit <= 0 || !isfinite(miter_limit))
|
||||
return;
|
||||
// other values must change the current value to the new value.
|
||||
my_drawing_state().miter_limit = miter_limit;
|
||||
}
|
||||
float miter_limit() const
|
||||
{
|
||||
// On getting, it must return the current value.
|
||||
return my_drawing_state().miter_limit;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-setlinedash
|
||||
void set_line_dash(Vector<double> segments)
|
||||
{
|
||||
// The setLineDash(segments) method, when invoked, must run these steps:
|
||||
|
||||
// 1. If any value in segments is not finite (e.g. an Infinity or a NaN value), or if any value is negative (less than zero), then return
|
||||
// (without throwing an exception; user agents could show a message on a developer console, though, as that would be helpful for debugging).
|
||||
for (auto const& segment : segments) {
|
||||
if (!isfinite(segment) || segment < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If the number of elements in segments is odd, then let segments be the concatenation of two copies of segments.
|
||||
if (segments.size() % 2 == 1)
|
||||
segments.extend(segments);
|
||||
|
||||
// 3. Let the object's dash list be segments.
|
||||
my_drawing_state().dash_list = segments;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-getlinedash
|
||||
Vector<double> get_line_dash()
|
||||
{
|
||||
// When the getLineDash() method is invoked, it must return a sequence whose values are the values of the object's dash list, in the same order.
|
||||
return my_drawing_state().dash_list;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linedashoffset
|
||||
void set_line_dash_offset(float line_dash_offset)
|
||||
{
|
||||
// On setting, infinite and NaN values must be ignored, leaving the value unchanged;
|
||||
if (!isfinite(line_dash_offset))
|
||||
return;
|
||||
// other values must change the current value to the new value.
|
||||
my_drawing_state().line_dash_offset = line_dash_offset;
|
||||
}
|
||||
float line_dash_offset() const
|
||||
{
|
||||
// On getting, it must return the current value.
|
||||
return my_drawing_state().line_dash_offset;
|
||||
}
|
||||
|
||||
protected:
|
||||
CanvasPathDrawingStyles() = default;
|
||||
|
||||
private:
|
||||
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
|
||||
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
|
||||
};
|
||||
|
||||
}
|
15
Libraries/LibWeb/HTML/Canvas/CanvasPathDrawingStyles.idl
Normal file
15
Libraries/LibWeb/HTML/Canvas/CanvasPathDrawingStyles.idl
Normal file
|
@ -0,0 +1,15 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvaslinecap
|
||||
// enum CanvasLineCap { "butt", "round", "square" };
|
||||
// enum CanvasLineJoin { "round", "bevel", "miter" };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspathdrawingstyles
|
||||
interface mixin CanvasPathDrawingStyles {
|
||||
attribute unrestricted double lineWidth;
|
||||
attribute CanvasLineCap lineCap;
|
||||
attribute CanvasLineJoin lineJoin;
|
||||
attribute unrestricted double miterLimit;
|
||||
|
||||
undefined setLineDash(sequence<unrestricted double> segments);
|
||||
sequence<unrestricted double> getLineDash();
|
||||
attribute unrestricted double lineDashOffset;
|
||||
};
|
24
Libraries/LibWeb/HTML/Canvas/CanvasRect.h
Normal file
24
Libraries/LibWeb/HTML/Canvas/CanvasRect.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasrect
|
||||
class CanvasRect {
|
||||
public:
|
||||
virtual ~CanvasRect() = default;
|
||||
|
||||
virtual void fill_rect(float x, float y, float width, float height) = 0;
|
||||
virtual void stroke_rect(float x, float y, float width, float height) = 0;
|
||||
virtual void clear_rect(float x, float y, float width, float height) = 0;
|
||||
|
||||
protected:
|
||||
CanvasRect() = default;
|
||||
};
|
||||
|
||||
}
|
6
Libraries/LibWeb/HTML/Canvas/CanvasRect.idl
Normal file
6
Libraries/LibWeb/HTML/Canvas/CanvasRect.idl
Normal file
|
@ -0,0 +1,6 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasrect
|
||||
interface mixin CanvasRect {
|
||||
undefined clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
|
||||
undefined fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
|
||||
undefined strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
|
||||
};
|
40
Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.h
Normal file
40
Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2024, İbrahim UYSAL <uysalibov@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasshadowstyles
|
||||
template<typename IncludingClass>
|
||||
class CanvasShadowStyles {
|
||||
public:
|
||||
~CanvasShadowStyles() = default;
|
||||
|
||||
virtual float shadow_offset_x() const = 0;
|
||||
virtual void set_shadow_offset_x(float offsetX) = 0;
|
||||
|
||||
virtual float shadow_offset_y() const = 0;
|
||||
virtual void set_shadow_offset_y(float offsetY) = 0;
|
||||
|
||||
virtual String shadow_color() const = 0;
|
||||
virtual void set_shadow_color(String color) = 0;
|
||||
|
||||
protected:
|
||||
CanvasShadowStyles() = default;
|
||||
|
||||
private:
|
||||
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
|
||||
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
|
||||
};
|
||||
|
||||
}
|
8
Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.idl
Normal file
8
Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.idl
Normal file
|
@ -0,0 +1,8 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasshadowstyles
|
||||
interface mixin CanvasShadowStyles {
|
||||
// shadows
|
||||
attribute unrestricted double shadowOffsetX; // (default 0)
|
||||
attribute unrestricted double shadowOffsetY; // (default 0)
|
||||
[FIXME] attribute unrestricted double shadowBlur; // (default 0)
|
||||
attribute DOMString shadowColor; // (default transparent black)
|
||||
};
|
75
Libraries/LibWeb/HTML/Canvas/CanvasState.cpp
Normal file
75
Libraries/LibWeb/HTML/Canvas/CanvasState.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-save
|
||||
void CanvasState::save()
|
||||
{
|
||||
// The save() method steps are to push a copy of the current drawing state onto the drawing state stack.
|
||||
m_drawing_state_stack.append(m_drawing_state);
|
||||
|
||||
if (auto* painter = painter_for_canvas_state())
|
||||
painter->save();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-restore
|
||||
void CanvasState::restore()
|
||||
{
|
||||
// The restore() method steps are to pop the top entry in the drawing state stack, and reset the drawing state it describes. If there is no saved state, then the method must do nothing.
|
||||
if (m_drawing_state_stack.is_empty())
|
||||
return;
|
||||
m_drawing_state = m_drawing_state_stack.take_last();
|
||||
|
||||
if (auto* painter = painter_for_canvas_state())
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-reset
|
||||
void CanvasState::reset()
|
||||
{
|
||||
// The reset() method steps are to reset the rendering context to its default state.
|
||||
reset_to_default_state();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-iscontextlost
|
||||
bool CanvasState::is_context_lost()
|
||||
{
|
||||
// The isContextLost() method steps are to return this's context lost.
|
||||
return m_context_lost;
|
||||
}
|
||||
|
||||
NonnullRefPtr<Gfx::PaintStyle> CanvasState::FillOrStrokeStyle::to_gfx_paint_style()
|
||||
{
|
||||
return m_fill_or_stroke_style.visit(
|
||||
[&](Gfx::Color color) -> NonnullRefPtr<Gfx::PaintStyle> {
|
||||
if (!m_color_paint_style)
|
||||
m_color_paint_style = Gfx::SolidColorPaintStyle::create(color).release_value_but_fixme_should_propagate_errors();
|
||||
return m_color_paint_style.release_nonnull();
|
||||
},
|
||||
[&](auto handle) {
|
||||
return handle->to_gfx_paint_style();
|
||||
});
|
||||
}
|
||||
|
||||
Gfx::Color CanvasState::FillOrStrokeStyle::to_color_but_fixme_should_accept_any_paint_style() const
|
||||
{
|
||||
return as_color().value_or(Gfx::Color::Black);
|
||||
}
|
||||
|
||||
Optional<Gfx::Color> CanvasState::FillOrStrokeStyle::as_color() const
|
||||
{
|
||||
if (auto* color = m_fill_or_stroke_style.get_pointer<Gfx::Color>())
|
||||
return *color;
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
119
Libraries/LibWeb/HTML/Canvas/CanvasState.h
Normal file
119
Libraries/LibWeb/HTML/Canvas/CanvasState.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/AffineTransform.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Font/Font.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibGfx/Path.h>
|
||||
#include <LibGfx/WindingRule.h>
|
||||
#include <LibWeb/Bindings/CanvasRenderingContext2DPrototype.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasstate
|
||||
class CanvasState {
|
||||
public:
|
||||
virtual ~CanvasState() = default;
|
||||
|
||||
virtual Gfx::Painter* painter_for_canvas_state() = 0;
|
||||
virtual Gfx::Path& path_for_canvas_state() = 0;
|
||||
|
||||
void save();
|
||||
void restore();
|
||||
void reset();
|
||||
bool is_context_lost();
|
||||
|
||||
using FillOrStrokeVariant = Variant<Gfx::Color, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
|
||||
|
||||
struct FillOrStrokeStyle {
|
||||
FillOrStrokeStyle(Gfx::Color color)
|
||||
: m_fill_or_stroke_style(color)
|
||||
{
|
||||
}
|
||||
|
||||
FillOrStrokeStyle(JS::Handle<CanvasGradient> gradient)
|
||||
: m_fill_or_stroke_style(gradient)
|
||||
{
|
||||
}
|
||||
|
||||
FillOrStrokeStyle(JS::Handle<CanvasPattern> pattern)
|
||||
: m_fill_or_stroke_style(pattern)
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style();
|
||||
|
||||
Optional<Gfx::Color> as_color() const;
|
||||
Gfx::Color to_color_but_fixme_should_accept_any_paint_style() const;
|
||||
|
||||
using JsFillOrStrokeStyle = Variant<String, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
|
||||
|
||||
JsFillOrStrokeStyle to_js_fill_or_stroke_style() const
|
||||
{
|
||||
return m_fill_or_stroke_style.visit(
|
||||
[&](Gfx::Color color) -> JsFillOrStrokeStyle {
|
||||
return color.to_string(Gfx::Color::HTMLCompatibleSerialization::Yes);
|
||||
},
|
||||
[&](auto handle) -> JsFillOrStrokeStyle {
|
||||
return handle;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
FillOrStrokeVariant m_fill_or_stroke_style;
|
||||
RefPtr<Gfx::PaintStyle> m_color_paint_style { nullptr };
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#drawing-state
|
||||
struct DrawingState {
|
||||
Gfx::AffineTransform transform;
|
||||
FillOrStrokeStyle fill_style { Gfx::Color::Black };
|
||||
FillOrStrokeStyle stroke_style { Gfx::Color::Black };
|
||||
float shadow_offset_x { 0.0f };
|
||||
float shadow_offset_y { 0.0f };
|
||||
Gfx::Color shadow_color { Gfx::Color::Transparent };
|
||||
float line_width { 1 };
|
||||
Bindings::CanvasLineCap line_cap { Bindings::CanvasLineCap::Butt };
|
||||
Bindings::CanvasLineJoin line_join { Bindings::CanvasLineJoin::Miter };
|
||||
float miter_limit { 10 };
|
||||
Vector<double> dash_list;
|
||||
float line_dash_offset { 0 };
|
||||
bool image_smoothing_enabled { true };
|
||||
Bindings::ImageSmoothingQuality image_smoothing_quality { Bindings::ImageSmoothingQuality::Low };
|
||||
float global_alpha = { 1 };
|
||||
RefPtr<CSS::CSSStyleValue> font_style_value { nullptr };
|
||||
RefPtr<Gfx::Font const> current_font { nullptr };
|
||||
Bindings::CanvasTextAlign text_align { Bindings::CanvasTextAlign::Start };
|
||||
Bindings::CanvasTextBaseline text_baseline { Bindings::CanvasTextBaseline::Alphabetic };
|
||||
};
|
||||
DrawingState& drawing_state() { return m_drawing_state; }
|
||||
DrawingState const& drawing_state() const { return m_drawing_state; }
|
||||
|
||||
void clear_drawing_state_stack() { m_drawing_state_stack.clear(); }
|
||||
void reset_drawing_state() { m_drawing_state = {}; }
|
||||
|
||||
virtual void reset_to_default_state() = 0;
|
||||
|
||||
protected:
|
||||
CanvasState() = default;
|
||||
|
||||
private:
|
||||
DrawingState m_drawing_state;
|
||||
Vector<DrawingState> m_drawing_state_stack;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-context-lost
|
||||
bool m_context_lost { false };
|
||||
};
|
||||
|
||||
}
|
7
Libraries/LibWeb/HTML/Canvas/CanvasState.idl
Normal file
7
Libraries/LibWeb/HTML/Canvas/CanvasState.idl
Normal file
|
@ -0,0 +1,7 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasstate
|
||||
interface mixin CanvasState {
|
||||
undefined save();
|
||||
undefined restore();
|
||||
undefined reset();
|
||||
boolean isContextLost();
|
||||
};
|
28
Libraries/LibWeb/HTML/Canvas/CanvasText.h
Normal file
28
Libraries/LibWeb/HTML/Canvas/CanvasText.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibWeb/HTML/TextMetrics.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastext
|
||||
class CanvasText {
|
||||
public:
|
||||
virtual ~CanvasText() = default;
|
||||
|
||||
virtual void fill_text(StringView, float x, float y, Optional<double> max_width) = 0;
|
||||
virtual void stroke_text(StringView, float x, float y, Optional<double> max_width) = 0;
|
||||
virtual JS::NonnullGCPtr<TextMetrics> measure_text(StringView text) = 0;
|
||||
|
||||
protected:
|
||||
CanvasText() = default;
|
||||
};
|
||||
|
||||
}
|
8
Libraries/LibWeb/HTML/Canvas/CanvasText.idl
Normal file
8
Libraries/LibWeb/HTML/Canvas/CanvasText.idl
Normal file
|
@ -0,0 +1,8 @@
|
|||
#import <HTML/TextMetrics.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastext
|
||||
interface mixin CanvasText {
|
||||
undefined fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
|
||||
undefined strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
|
||||
TextMetrics measureText(DOMString text);
|
||||
};
|
82
Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h
Normal file
82
Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastextdrawingstyles
|
||||
template<typename IncludingClass>
|
||||
class CanvasTextDrawingStyles {
|
||||
public:
|
||||
~CanvasTextDrawingStyles() = default;
|
||||
|
||||
ByteString font() const
|
||||
{
|
||||
// When font style value is empty return default string
|
||||
if (!my_drawing_state().font_style_value) {
|
||||
return "10px sans-serif";
|
||||
}
|
||||
|
||||
// On getting, the font attribute must return the serialized form of the current font of the context (with no 'line-height' component).
|
||||
auto const& font_style_value = my_drawing_state().font_style_value->as_shorthand();
|
||||
auto font_style = font_style_value.longhand(CSS::PropertyID::FontStyle);
|
||||
auto font_weight = font_style_value.longhand(CSS::PropertyID::FontWeight);
|
||||
auto font_size = font_style_value.longhand(CSS::PropertyID::FontSize);
|
||||
auto font_family = font_style_value.longhand(CSS::PropertyID::FontFamily);
|
||||
return ByteString::formatted("{} {} {} {}", font_style->to_string(), font_weight->to_string(), font_size->to_string(), font_family->to_string());
|
||||
}
|
||||
|
||||
void set_font(StringView font)
|
||||
{
|
||||
// The font IDL attribute, on setting, must be parsed as a CSS <'font'> value (but without supporting property-independent style sheet syntax like 'inherit'),
|
||||
// and the resulting font must be assigned to the context, with the 'line-height' component forced to 'normal', with the 'font-size' component converted to CSS pixels,
|
||||
// and with system fonts being computed to explicit values.
|
||||
// FIXME: with the 'line-height' component forced to 'normal'
|
||||
// FIXME: with the 'font-size' component converted to CSS pixels
|
||||
auto parsing_context = CSS::Parser::ParsingContext { reinterpret_cast<IncludingClass&>(*this).realm() };
|
||||
auto font_style_value_result = parse_css_value(parsing_context, font, CSS::PropertyID::Font);
|
||||
|
||||
// If the new value is syntactically incorrect (including using property-independent style sheet syntax like 'inherit' or 'initial'), then it must be ignored, without assigning a new font value.
|
||||
// NOTE: ShorthandStyleValue should be the only valid option here. We implicitly VERIFY this below.
|
||||
if (!font_style_value_result || !font_style_value_result->is_shorthand()) {
|
||||
return;
|
||||
}
|
||||
my_drawing_state().font_style_value = font_style_value_result.release_nonnull();
|
||||
|
||||
// Load font with font style value properties
|
||||
auto const& font_style_value = my_drawing_state().font_style_value->as_shorthand();
|
||||
auto& canvas_element = reinterpret_cast<IncludingClass&>(*this).canvas_element();
|
||||
auto& font_style = *font_style_value.longhand(CSS::PropertyID::FontStyle);
|
||||
auto& font_weight = *font_style_value.longhand(CSS::PropertyID::FontWeight);
|
||||
auto& font_width = *font_style_value.longhand(CSS::PropertyID::FontWidth);
|
||||
auto& font_size = *font_style_value.longhand(CSS::PropertyID::FontSize);
|
||||
auto& font_family = *font_style_value.longhand(CSS::PropertyID::FontFamily);
|
||||
auto font_list = canvas_element.document().style_computer().compute_font_for_style_values(&canvas_element, {}, font_family, font_size, font_style, font_weight, font_width);
|
||||
my_drawing_state().current_font = font_list->first();
|
||||
}
|
||||
|
||||
Bindings::CanvasTextAlign text_align() const { return my_drawing_state().text_align; }
|
||||
void set_text_align(Bindings::CanvasTextAlign text_align) { my_drawing_state().text_align = text_align; }
|
||||
|
||||
Bindings::CanvasTextBaseline text_baseline() const { return my_drawing_state().text_baseline; }
|
||||
void set_text_baseline(Bindings::CanvasTextBaseline text_baseline) { my_drawing_state().text_baseline = text_baseline; }
|
||||
|
||||
protected:
|
||||
CanvasTextDrawingStyles() = default;
|
||||
|
||||
private:
|
||||
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
|
||||
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
|
||||
};
|
||||
|
||||
}
|
24
Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.idl
Normal file
24
Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.idl
Normal file
|
@ -0,0 +1,24 @@
|
|||
#import <HTML/CanvasRenderingContext2D.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastextalign
|
||||
// enum CanvasTextAlign { "start", "end", "left", "right", "center" };
|
||||
// enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" };
|
||||
enum CanvasDirection { "ltr", "rtl", "inherit" };
|
||||
enum CanvasFontKerning { "auto", "normal", "none" };
|
||||
enum CanvasFontStretch { "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "normal", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" };
|
||||
enum CanvasFontVariantCaps { "normal", "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "unicase", "titling-caps" };
|
||||
enum CanvasTextRendering { "auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision" };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastextdrawingstyles
|
||||
interface mixin CanvasTextDrawingStyles {
|
||||
attribute DOMString font; // (default 10px sans-serif)
|
||||
attribute CanvasTextAlign textAlign; // (default: "start")
|
||||
attribute CanvasTextBaseline textBaseline; // (default: "alphabetic")
|
||||
[FIXME] attribute CanvasDirection direction; // (default: "inherit")
|
||||
[FIXME] attribute DOMString letterSpacing; // (default: "0px")
|
||||
[FIXME] attribute CanvasFontKerning fontKerning; // (default: "auto")
|
||||
[FIXME] attribute CanvasFontStretch fontStretch; // (default: "normal")
|
||||
[FIXME] attribute CanvasFontVariantCaps fontVariantCaps; // (default: "normal")
|
||||
[FIXME] attribute CanvasTextRendering textRendering; // (default: "auto")
|
||||
[FIXME] attribute DOMString wordSpacing; // (default: "0px")
|
||||
};
|
146
Libraries/LibWeb/HTML/Canvas/CanvasTransform.h
Normal file
146
Libraries/LibWeb/HTML/Canvas/CanvasTransform.h
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibWeb/Geometry/DOMMatrix.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasPath.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastransform
|
||||
template<typename IncludingClass>
|
||||
class CanvasTransform {
|
||||
public:
|
||||
~CanvasTransform() = default;
|
||||
|
||||
Gfx::Path& mutable_path() { return static_cast<IncludingClass&>(*this).path(); }
|
||||
|
||||
void scale(float sx, float sy)
|
||||
{
|
||||
dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasTransform::scale({}, {})", sx, sy);
|
||||
if (!isfinite(sx) || !isfinite(sy))
|
||||
return;
|
||||
my_drawing_state().transform.scale(sx, sy);
|
||||
flush_transform();
|
||||
mutable_path().transform(Gfx::AffineTransform().scale(1.0 / sx, 1.0 / sy));
|
||||
}
|
||||
|
||||
void translate(float tx, float ty)
|
||||
{
|
||||
dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasTransform::translate({}, {})", tx, ty);
|
||||
if (!isfinite(tx) || !isfinite(ty))
|
||||
return;
|
||||
my_drawing_state().transform.translate(tx, ty);
|
||||
flush_transform();
|
||||
mutable_path().transform(Gfx::AffineTransform().translate(-tx, -ty));
|
||||
}
|
||||
|
||||
void rotate(float radians)
|
||||
{
|
||||
dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasTransform::rotate({})", radians);
|
||||
if (!isfinite(radians))
|
||||
return;
|
||||
my_drawing_state().transform.rotate_radians(radians);
|
||||
flush_transform();
|
||||
mutable_path().transform(Gfx::AffineTransform().rotate_radians(-radians));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-transform
|
||||
void transform(double a, double b, double c, double d, double e, double f)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(a) || !isfinite(b) || !isfinite(c) || !isfinite(d) || !isfinite(e) || !isfinite(f))
|
||||
return;
|
||||
|
||||
// 2. Replace the current transformation matrix with the result of multiplying the current transformation matrix with the matrix described by:
|
||||
// a c e
|
||||
// b d f
|
||||
// 0 0 1
|
||||
auto transform = Gfx::AffineTransform(a, b, c, d, e, f);
|
||||
my_drawing_state().transform.multiply(transform);
|
||||
|
||||
if (auto inverse = transform.inverse(); inverse.has_value()) {
|
||||
mutable_path().transform(inverse.value());
|
||||
}
|
||||
flush_transform();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-gettransform
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<Geometry::DOMMatrix>> get_transform()
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
auto transform = my_drawing_state().transform;
|
||||
Geometry::DOMMatrix2DInit init = { transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f(), {}, {}, {}, {}, {}, {} };
|
||||
return Geometry::DOMMatrix::create_from_dom_matrix_2d_init(realm, init);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-settransform
|
||||
void set_transform(double a, double b, double c, double d, double e, double f)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(a) || !isfinite(b) || !isfinite(c) || !isfinite(d) || !isfinite(e) || !isfinite(f))
|
||||
return;
|
||||
|
||||
// 2. Reset the current transformation matrix to the identity matrix.
|
||||
my_drawing_state().transform = {};
|
||||
flush_transform();
|
||||
|
||||
// 3. Invoke the transform(a, b, c, d, e, f) method with the same arguments.
|
||||
transform(a, b, c, d, e, f);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-settransform-matrix
|
||||
WebIDL::ExceptionOr<void> set_transform(Geometry::DOMMatrix2DInit& init)
|
||||
{
|
||||
// 1. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform.
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
auto matrix = TRY(Geometry::DOMMatrix::create_from_dom_matrix_2d_init(realm, init));
|
||||
|
||||
// 2. If one or more of matrix's m11 element, m12 element, m21 element, m22 element, m41 element, or m42 element are infinite or NaN, then return.
|
||||
if (!isfinite(matrix->m11()) || !isfinite(matrix->m12()) || !isfinite(matrix->m21()) || !isfinite(matrix->m22()) || !isfinite(matrix->m41()) || !isfinite(matrix->m42()))
|
||||
return {};
|
||||
|
||||
auto original_transform = my_drawing_state().transform;
|
||||
|
||||
// 3. Reset the current transformation matrix to matrix.
|
||||
auto transform = Gfx::AffineTransform { static_cast<float>(matrix->a()), static_cast<float>(matrix->b()), static_cast<float>(matrix->c()), static_cast<float>(matrix->d()), static_cast<float>(matrix->e()), static_cast<float>(matrix->f()) };
|
||||
my_drawing_state().transform = transform;
|
||||
|
||||
mutable_path().transform(original_transform);
|
||||
|
||||
flush_transform();
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-resettransform
|
||||
void reset_transform()
|
||||
{
|
||||
// The resetTransform() method, when invoked, must reset the current transformation matrix to the identity matrix.
|
||||
my_drawing_state().transform = {};
|
||||
flush_transform();
|
||||
}
|
||||
|
||||
void flush_transform()
|
||||
{
|
||||
if (auto* painter = static_cast<IncludingClass&>(*this).painter())
|
||||
painter->set_transform(my_drawing_state().transform);
|
||||
}
|
||||
|
||||
protected:
|
||||
CanvasTransform() = default;
|
||||
|
||||
private:
|
||||
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
|
||||
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
|
||||
};
|
||||
|
||||
}
|
14
Libraries/LibWeb/HTML/Canvas/CanvasTransform.idl
Normal file
14
Libraries/LibWeb/HTML/Canvas/CanvasTransform.idl
Normal file
|
@ -0,0 +1,14 @@
|
|||
#import <Geometry/DOMMatrix.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvastransform
|
||||
interface mixin CanvasTransform {
|
||||
undefined scale(unrestricted double x, unrestricted double y);
|
||||
undefined rotate(unrestricted double radians);
|
||||
undefined translate(unrestricted double x, unrestricted double y);
|
||||
undefined transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
|
||||
|
||||
[NewObject] DOMMatrix getTransform();
|
||||
undefined setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
|
||||
undefined setTransform(optional DOMMatrix2DInit transform = {});
|
||||
undefined resetTransform();
|
||||
};
|
5
Libraries/LibWeb/HTML/Canvas/CanvasUserInterface.idl
Normal file
5
Libraries/LibWeb/HTML/Canvas/CanvasUserInterface.idl
Normal file
|
@ -0,0 +1,5 @@
|
|||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasuserinterface
|
||||
interface mixin CanvasUserInterface {
|
||||
[FIXME] undefined drawFocusIfNeeded(Element element);
|
||||
[FIXME] undefined drawFocusIfNeeded(Path2D path, Element element);
|
||||
};
|
83
Libraries/LibWeb/HTML/CanvasGradient.cpp
Normal file
83
Libraries/LibWeb/HTML/CanvasGradient.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibWeb/Bindings/CanvasGradientPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CanvasGradient);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createradialgradient
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> CanvasGradient::create_radial(JS::Realm& realm, double x0, double y0, double r0, double x1, double y1, double r1)
|
||||
{
|
||||
// If either of r0 or r1 are negative, then an "IndexSizeError" DOMException must be thrown.
|
||||
if (r0 < 0)
|
||||
return WebIDL::IndexSizeError::create(realm, "The r0 passed is less than 0"_string);
|
||||
if (r1 < 0)
|
||||
return WebIDL::IndexSizeError::create(realm, "The r1 passed is less than 0"_string);
|
||||
|
||||
auto radial_gradient = TRY_OR_THROW_OOM(realm.vm(), Gfx::CanvasRadialGradientPaintStyle::create(Gfx::FloatPoint { x0, y0 }, r0, Gfx::FloatPoint { x1, y1 }, r1));
|
||||
return realm.heap().allocate<CanvasGradient>(realm, realm, *radial_gradient);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createlineargradient
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> CanvasGradient::create_linear(JS::Realm& realm, double x0, double y0, double x1, double y1)
|
||||
{
|
||||
auto linear_gradient = TRY_OR_THROW_OOM(realm.vm(), Gfx::CanvasLinearGradientPaintStyle::create(Gfx::FloatPoint { x0, y0 }, Gfx::FloatPoint { x1, y1 }));
|
||||
return realm.heap().allocate<CanvasGradient>(realm, realm, *linear_gradient);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createconicgradient
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> CanvasGradient::create_conic(JS::Realm& realm, double start_angle, double x, double y)
|
||||
{
|
||||
auto conic_gradient = TRY_OR_THROW_OOM(realm.vm(), Gfx::CanvasConicGradientPaintStyle::create(Gfx::FloatPoint { x, y }, start_angle));
|
||||
return realm.heap().allocate<CanvasGradient>(realm, realm, *conic_gradient);
|
||||
}
|
||||
|
||||
CanvasGradient::CanvasGradient(JS::Realm& realm, Gfx::GradientPaintStyle& gradient)
|
||||
: PlatformObject(realm)
|
||||
, m_gradient(gradient)
|
||||
{
|
||||
}
|
||||
|
||||
CanvasGradient::~CanvasGradient() = default;
|
||||
|
||||
void CanvasGradient::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CanvasGradient);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvasgradient-addcolorstop
|
||||
WebIDL::ExceptionOr<void> CanvasGradient::add_color_stop(double offset, StringView color)
|
||||
{
|
||||
// 1. If the offset is less than 0 or greater than 1, then throw an "IndexSizeError" DOMException.
|
||||
if (offset < 0 || offset > 1)
|
||||
return WebIDL::IndexSizeError::create(realm(), "CanvasGradient color stop offset out of bounds"_string);
|
||||
|
||||
// 2. Let parsed color be the result of parsing color.
|
||||
auto parsed_color = Color::from_string(color);
|
||||
|
||||
// 3. If parsed color is failure, throw a "SyntaxError" DOMException.
|
||||
if (!parsed_color.has_value())
|
||||
return WebIDL::SyntaxError::create(realm(), "Could not parse color for CanvasGradient"_string);
|
||||
|
||||
// 4. Place a new stop on the gradient, at offset offset relative to the whole gradient, and with the color parsed color.
|
||||
TRY_OR_THROW_OOM(realm().vm(), m_gradient->add_color_stop(offset, parsed_color.value()));
|
||||
|
||||
// FIXME: If multiple stops are added at the same offset on a gradient, then they must be placed in the order added,
|
||||
// with the first one closest to the start of the gradient, and each subsequent one infinitesimally further along
|
||||
// towards the end point (in effect causing all but the first and last stop added at each point to be ignored).
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
38
Libraries/LibWeb/HTML/CanvasGradient.h
Normal file
38
Libraries/LibWeb/HTML/CanvasGradient.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class CanvasGradient final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(CanvasGradient, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(CanvasGradient);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> create_radial(JS::Realm&, double x0, double y0, double r0, double x1, double y1, double r1);
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> create_linear(JS::Realm&, double x0, double y0, double x1, double y1);
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> create_conic(JS::Realm&, double start_angle, double x, double y);
|
||||
|
||||
WebIDL::ExceptionOr<void> add_color_stop(double offset, StringView color);
|
||||
|
||||
~CanvasGradient();
|
||||
|
||||
NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style() { return m_gradient; }
|
||||
|
||||
private:
|
||||
CanvasGradient(JS::Realm&, Gfx::GradientPaintStyle& gradient);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
NonnullRefPtr<Gfx::GradientPaintStyle> m_gradient;
|
||||
};
|
||||
|
||||
}
|
6
Libraries/LibWeb/HTML/CanvasGradient.idl
Normal file
6
Libraries/LibWeb/HTML/CanvasGradient.idl
Normal file
|
@ -0,0 +1,6 @@
|
|||
// https://html.spec.whatwg.org/#canvasgradient
|
||||
[Exposed=(Window,Worker)]
|
||||
interface CanvasGradient {
|
||||
// opaque object
|
||||
undefined addColorStop(double offset, DOMString color);
|
||||
};
|
153
Libraries/LibWeb/HTML/CanvasPattern.cpp
Normal file
153
Libraries/LibWeb/HTML/CanvasPattern.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibWeb/Bindings/CanvasPatternPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
|
||||
#include <LibWeb/HTML/ImageBitmap.h>
|
||||
#include <LibWeb/SVG/SVGImageElement.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CanvasPattern);
|
||||
|
||||
void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const
|
||||
{
|
||||
// 1. Create an infinite transparent black bitmap.
|
||||
// *waves magic wand 🪄*
|
||||
// Done!
|
||||
|
||||
// 2. Place a copy of the image on the bitmap, anchored such that its top left corner
|
||||
// is at the origin of the coordinate space, with one coordinate space unit per CSS pixel of the image,
|
||||
// then place repeated copies of this image horizontally to the left and right, if the repetition behavior
|
||||
// is "repeat-x", or vertically up and down, if the repetition behavior is "repeat-y", or in all four directions
|
||||
// all over the bitmap, if the repetition behavior is "repeat".
|
||||
|
||||
// FIMXE: If the original image data is a bitmap image, then the value painted at a point in the area of
|
||||
// the repetitions is computed by filtering the original image data. When scaling up, if the imageSmoothingEnabled
|
||||
// attribute is set to false, then the image must be rendered using nearest-neighbor interpolation.
|
||||
// Otherwise, the user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor).
|
||||
// User agents which support multiple filtering algorithms may use the value of the imageSmoothingQuality attribute
|
||||
// to guide the choice of filtering algorithm. When such a filtering algorithm requires a pixel value from outside
|
||||
// the original image data, it must instead use the value from wrapping the pixel's coordinates to the original
|
||||
// image's dimensions. (That is, the filter uses 'repeat' behavior, regardless of the value of the pattern's repetition behavior.)
|
||||
|
||||
// FIXME: 3. Transform the resulting bitmap according to the pattern's transformation matrix.
|
||||
|
||||
// FIXME: 4. Transform the resulting bitmap again, this time according to the current transformation matrix.
|
||||
|
||||
// 5. Replace any part of the image outside the area in which the pattern is to be rendered with transparent black.
|
||||
|
||||
// 6. The resulting bitmap is what is to be rendered, with the same origin and same scale.
|
||||
|
||||
auto const bitmap_width = m_immutable_bitmap->width();
|
||||
auto const bitmap_height = m_immutable_bitmap->height();
|
||||
|
||||
paint([=, this](auto point) {
|
||||
point.translate_by(physical_bounding_box.location());
|
||||
point = [&]() -> Gfx::IntPoint {
|
||||
switch (m_repetition) {
|
||||
case Repetition::NoRepeat: {
|
||||
return point;
|
||||
}
|
||||
case Repetition::Repeat: {
|
||||
return {
|
||||
point.x() % bitmap_width,
|
||||
point.y() % bitmap_height
|
||||
};
|
||||
}
|
||||
case Repetition::RepeatX: {
|
||||
return {
|
||||
point.x() % bitmap_width,
|
||||
point.y()
|
||||
};
|
||||
}
|
||||
case Repetition::RepeatY: {
|
||||
return {
|
||||
point.x(),
|
||||
point.y() % bitmap_height
|
||||
};
|
||||
}
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
if (m_immutable_bitmap->rect().contains(point))
|
||||
return m_immutable_bitmap->get_pixel(point.x(), point.y());
|
||||
return Gfx::Color();
|
||||
});
|
||||
}
|
||||
|
||||
CanvasPattern::CanvasPattern(JS::Realm& realm, CanvasPatternPaintStyle& pattern)
|
||||
: PlatformObject(realm)
|
||||
, m_pattern(pattern)
|
||||
{
|
||||
}
|
||||
|
||||
CanvasPattern::~CanvasPattern() = default;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createpattern
|
||||
WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition)
|
||||
{
|
||||
auto parse_repetition = [&](auto repetition) -> Optional<CanvasPatternPaintStyle::Repetition> {
|
||||
if (repetition == "repeat"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::Repeat;
|
||||
if (repetition == "repeat-x"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::RepeatX;
|
||||
if (repetition == "repeat-y"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::RepeatY;
|
||||
if (repetition == "no-repeat"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::NoRepeat;
|
||||
return {};
|
||||
};
|
||||
|
||||
// 1. Let usability be the result of checking the usability of image.
|
||||
auto usability = TRY(check_usability_of_image(image));
|
||||
|
||||
// 2. If usability is bad, then return null.
|
||||
if (usability == CanvasImageSourceUsability::Bad)
|
||||
return JS::GCPtr<CanvasPattern> {};
|
||||
|
||||
// 3. Assert: usability is good.
|
||||
VERIFY(usability == CanvasImageSourceUsability::Good);
|
||||
|
||||
// 4. If repetition is the empty string, then set it to "repeat".
|
||||
if (repetition.is_empty())
|
||||
repetition = "repeat"sv;
|
||||
|
||||
// 5. If repetition is not identical to one of "repeat", "repeat-x", "repeat-y", or "no-repeat",
|
||||
// then throw a "SyntaxError" DOMException.
|
||||
auto repetition_value = parse_repetition(repetition);
|
||||
if (!repetition_value.has_value())
|
||||
return WebIDL::SyntaxError::create(realm, "Repetition value is not valid"_string);
|
||||
|
||||
// Note: Bitmap won't be null here, as if it were it would have "bad" usability.
|
||||
auto bitmap = image.visit(
|
||||
[](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->immutable_bitmap(); },
|
||||
[](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->current_image_bitmap(); },
|
||||
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*source->surface()); },
|
||||
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
|
||||
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); });
|
||||
|
||||
// 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition.
|
||||
auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(*bitmap, *repetition_value));
|
||||
|
||||
// FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean.
|
||||
|
||||
// 8. Return pattern.
|
||||
return realm.heap().allocate<CanvasPattern>(realm, realm, *pattern);
|
||||
}
|
||||
|
||||
void CanvasPattern::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CanvasPattern);
|
||||
}
|
||||
|
||||
}
|
61
Libraries/LibWeb/HTML/CanvasPattern.h
Normal file
61
Libraries/LibWeb/HTML/CanvasPattern.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class CanvasPatternPaintStyle final : public Gfx::PaintStyle {
|
||||
public:
|
||||
enum class Repetition {
|
||||
Repeat,
|
||||
RepeatX,
|
||||
RepeatY,
|
||||
NoRepeat
|
||||
};
|
||||
|
||||
static ErrorOr<NonnullRefPtr<CanvasPatternPaintStyle>> create(Gfx::ImmutableBitmap const& bitmap, Repetition repetition)
|
||||
{
|
||||
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasPatternPaintStyle(bitmap, repetition));
|
||||
}
|
||||
|
||||
virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override;
|
||||
|
||||
private:
|
||||
CanvasPatternPaintStyle(Gfx::ImmutableBitmap const& immutable_bitmap, Repetition repetition)
|
||||
: m_immutable_bitmap(immutable_bitmap)
|
||||
, m_repetition(repetition)
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<Gfx::ImmutableBitmap> m_immutable_bitmap;
|
||||
Repetition m_repetition { Repetition::Repeat };
|
||||
};
|
||||
|
||||
class CanvasPattern final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(CanvasPattern, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(CanvasPattern);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create(JS::Realm&, CanvasImageSource const& image, StringView repetition);
|
||||
|
||||
~CanvasPattern();
|
||||
|
||||
NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style() { return m_pattern; }
|
||||
|
||||
private:
|
||||
CanvasPattern(JS::Realm&, CanvasPatternPaintStyle&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
NonnullRefPtr<CanvasPatternPaintStyle> m_pattern;
|
||||
};
|
||||
|
||||
}
|
6
Libraries/LibWeb/HTML/CanvasPattern.idl
Normal file
6
Libraries/LibWeb/HTML/CanvasPattern.idl
Normal file
|
@ -0,0 +1,6 @@
|
|||
// https://html.spec.whatwg.org/#canvaspattern
|
||||
[Exposed=(Window,Worker)]
|
||||
interface CanvasPattern {
|
||||
// opaque object
|
||||
[FIXME] undefined setTransform(optional DOMMatrix2DInit transform = {});
|
||||
};
|
837
Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
Normal file
837
Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
Normal file
|
@ -0,0 +1,837 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibGfx/DeprecatedPainter.h>
|
||||
#include <LibGfx/PainterSkia.h>
|
||||
#include <LibGfx/Quad.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibUnicode/Segmenter.h>
|
||||
#include <LibWeb/Bindings/CanvasRenderingContext2DPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
|
||||
#include <LibWeb/HTML/HTMLCanvasElement.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/ImageBitmap.h>
|
||||
#include <LibWeb/HTML/ImageData.h>
|
||||
#include <LibWeb/HTML/Path2D.h>
|
||||
#include <LibWeb/HTML/TextMetrics.h>
|
||||
#include <LibWeb/Infra/CharacterTypes.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
#include <LibWeb/Platform/FontPlugin.h>
|
||||
#include <LibWeb/SVG/SVGImageElement.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CanvasRenderingContext2D);
|
||||
|
||||
JS::NonnullGCPtr<CanvasRenderingContext2D> CanvasRenderingContext2D::create(JS::Realm& realm, HTMLCanvasElement& element)
|
||||
{
|
||||
return realm.heap().allocate<CanvasRenderingContext2D>(realm, realm, element);
|
||||
}
|
||||
|
||||
CanvasRenderingContext2D::CanvasRenderingContext2D(JS::Realm& realm, HTMLCanvasElement& element)
|
||||
: PlatformObject(realm)
|
||||
, CanvasPath(static_cast<Bindings::PlatformObject&>(*this), *this)
|
||||
, m_element(element)
|
||||
{
|
||||
}
|
||||
|
||||
CanvasRenderingContext2D::~CanvasRenderingContext2D() = default;
|
||||
|
||||
void CanvasRenderingContext2D::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
set_prototype(&Bindings::ensure_web_prototype<Bindings::CanvasRenderingContext2DPrototype>(realm, "CanvasRenderingContext2D"_string));
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_element);
|
||||
}
|
||||
|
||||
HTMLCanvasElement& CanvasRenderingContext2D::canvas_element()
|
||||
{
|
||||
return *m_element;
|
||||
}
|
||||
|
||||
HTMLCanvasElement const& CanvasRenderingContext2D::canvas_element() const
|
||||
{
|
||||
return *m_element;
|
||||
}
|
||||
|
||||
JS::NonnullGCPtr<HTMLCanvasElement> CanvasRenderingContext2D::canvas_for_binding() const
|
||||
{
|
||||
return *m_element;
|
||||
}
|
||||
|
||||
Gfx::Path CanvasRenderingContext2D::rect_path(float x, float y, float width, float height)
|
||||
{
|
||||
auto top_left = Gfx::FloatPoint(x, y);
|
||||
auto top_right = Gfx::FloatPoint(x + width, y);
|
||||
auto bottom_left = Gfx::FloatPoint(x, y + height);
|
||||
auto bottom_right = Gfx::FloatPoint(x + width, y + height);
|
||||
|
||||
Gfx::Path path;
|
||||
path.move_to(top_left);
|
||||
path.line_to(top_right);
|
||||
path.line_to(bottom_right);
|
||||
path.line_to(bottom_left);
|
||||
path.line_to(top_left);
|
||||
return path;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float height)
|
||||
{
|
||||
fill_internal(rect_path(x, y, width, height), Gfx::WindingRule::EvenOdd);
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float height)
|
||||
{
|
||||
if (auto* painter = this->painter()) {
|
||||
auto rect = Gfx::FloatRect(x, y, width, height);
|
||||
painter->clear_rect(rect, Color::Transparent);
|
||||
did_draw(rect);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height)
|
||||
{
|
||||
stroke_internal(rect_path(x, y, width, height));
|
||||
}
|
||||
|
||||
// 4.12.5.1.14 Drawing images, https://html.spec.whatwg.org/multipage/canvas.html#drawing-images
|
||||
WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasImageSource const& image, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height)
|
||||
{
|
||||
// 1. If any of the arguments are infinite or NaN, then return.
|
||||
if (!isfinite(source_x) || !isfinite(source_y) || !isfinite(source_width) || !isfinite(source_height) || !isfinite(destination_x) || !isfinite(destination_y) || !isfinite(destination_width) || !isfinite(destination_height))
|
||||
return {};
|
||||
|
||||
// 2. Let usability be the result of checking the usability of image.
|
||||
auto usability = TRY(check_usability_of_image(image));
|
||||
|
||||
// 3. If usability is bad, then return (without drawing anything).
|
||||
if (usability == CanvasImageSourceUsability::Bad)
|
||||
return {};
|
||||
|
||||
auto bitmap = image.visit(
|
||||
[](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
|
||||
return source->immutable_bitmap();
|
||||
},
|
||||
[](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
|
||||
return source->current_image_bitmap();
|
||||
},
|
||||
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
|
||||
auto surface = source->surface();
|
||||
if (!surface)
|
||||
return {};
|
||||
return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*surface);
|
||||
},
|
||||
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
|
||||
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
|
||||
return Gfx::ImmutableBitmap::create(*source->bitmap());
|
||||
});
|
||||
if (!bitmap)
|
||||
return {};
|
||||
|
||||
// 4. Establish the source and destination rectangles as follows:
|
||||
// If not specified, the dw and dh arguments must default to the values of sw and sh, interpreted such that one CSS pixel in the image is treated as one unit in the output bitmap's coordinate space.
|
||||
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
|
||||
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
|
||||
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
|
||||
// The source rectangle is the rectangle whose corners are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).
|
||||
// The destination rectangle is the rectangle whose corners are the four points (dx, dy), (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh).
|
||||
// NOTE: Implemented in drawImage() overloads
|
||||
|
||||
// The source rectangle is the rectangle whose corners are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).
|
||||
auto source_rect = Gfx::FloatRect { source_x, source_y, source_width, source_height };
|
||||
// The destination rectangle is the rectangle whose corners are the four points (dx, dy), (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh).
|
||||
auto destination_rect = Gfx::FloatRect { destination_x, destination_y, destination_width, destination_height };
|
||||
// When the source rectangle is outside the source image, the source rectangle must be clipped
|
||||
// to the source image and the destination rectangle must be clipped in the same proportion.
|
||||
auto clipped_source = source_rect.intersected(bitmap->rect().to_type<float>());
|
||||
auto clipped_destination = destination_rect;
|
||||
if (clipped_source != source_rect) {
|
||||
clipped_destination.set_width(clipped_destination.width() * (clipped_source.width() / source_rect.width()));
|
||||
clipped_destination.set_height(clipped_destination.height() * (clipped_source.height() / source_rect.height()));
|
||||
}
|
||||
|
||||
// 5. If one of the sw or sh arguments is zero, then return. Nothing is painted.
|
||||
if (source_width == 0 || source_height == 0)
|
||||
return {};
|
||||
|
||||
// 6. Paint the region of the image argument specified by the source rectangle on the region of the rendering context's output bitmap specified by the destination rectangle, after applying the current transformation matrix to the destination rectangle.
|
||||
auto scaling_mode = Gfx::ScalingMode::NearestNeighbor;
|
||||
if (drawing_state().image_smoothing_enabled) {
|
||||
// FIXME: Honor drawing_state().image_smoothing_quality
|
||||
scaling_mode = Gfx::ScalingMode::BilinearBlend;
|
||||
}
|
||||
|
||||
if (auto* painter = this->painter()) {
|
||||
painter->draw_bitmap(destination_rect, *bitmap, source_rect.to_rounded<int>(), scaling_mode, drawing_state().global_alpha);
|
||||
did_draw(destination_rect);
|
||||
}
|
||||
|
||||
// 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false.
|
||||
if (image_is_not_origin_clean(image))
|
||||
m_origin_clean = false;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::did_draw(Gfx::FloatRect const&)
|
||||
{
|
||||
// FIXME: Make use of the rect to reduce the invalidated area when possible.
|
||||
if (!canvas_element().paintable())
|
||||
return;
|
||||
canvas_element().paintable()->set_needs_display(InvalidateDisplayList::No);
|
||||
}
|
||||
|
||||
Gfx::Painter* CanvasRenderingContext2D::painter()
|
||||
{
|
||||
if (!canvas_element().surface()) {
|
||||
if (!canvas_element().allocate_painting_surface())
|
||||
return nullptr;
|
||||
canvas_element().document().invalidate_display_list();
|
||||
m_painter = make<Gfx::PainterSkia>(*canvas_element().surface());
|
||||
}
|
||||
return m_painter.ptr();
|
||||
}
|
||||
|
||||
Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y, Optional<double> max_width)
|
||||
{
|
||||
if (max_width.has_value() && max_width.value() <= 0)
|
||||
return {};
|
||||
|
||||
auto& drawing_state = this->drawing_state();
|
||||
auto font = current_font();
|
||||
|
||||
Gfx::Path path;
|
||||
path.move_to({ x, y });
|
||||
path.text(Utf8View { text }, *font);
|
||||
|
||||
auto text_width = path.bounding_box().width();
|
||||
Gfx::AffineTransform transform = {};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#text-preparation-algorithm:
|
||||
// 6. If maxWidth was provided and the hypothetical width of the inline box in the hypothetical line box
|
||||
// is greater than maxWidth CSS pixels, then change font to have a more condensed font (if one is
|
||||
// available or if a reasonably readable one can be synthesized by applying a horizontal scale
|
||||
// factor to the font) or a smaller font, and return to the previous step.
|
||||
if (max_width.has_value() && text_width > float(*max_width)) {
|
||||
auto horizontal_scale = float(*max_width) / text_width;
|
||||
transform = Gfx::AffineTransform {}.scale({ horizontal_scale, 1 });
|
||||
text_width *= horizontal_scale;
|
||||
}
|
||||
|
||||
// Apply text align
|
||||
// FIXME: CanvasTextAlign::Start and CanvasTextAlign::End currently do not nothing for right-to-left languages:
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textalign-start
|
||||
// Default alignment of draw_text is left so do nothing by CanvasTextAlign::Start and CanvasTextAlign::Left
|
||||
if (drawing_state.text_align == Bindings::CanvasTextAlign::Center) {
|
||||
transform = Gfx::AffineTransform {}.set_translation({ -text_width / 2, 0 }).multiply(transform);
|
||||
}
|
||||
if (drawing_state.text_align == Bindings::CanvasTextAlign::End || drawing_state.text_align == Bindings::CanvasTextAlign::Right) {
|
||||
transform = Gfx::AffineTransform {}.set_translation({ -text_width, 0 }).multiply(transform);
|
||||
}
|
||||
|
||||
// Apply text baseline
|
||||
// FIXME: Implement CanvasTextBasline::Hanging, Bindings::CanvasTextAlign::Alphabetic and Bindings::CanvasTextAlign::Ideographic for real
|
||||
// right now they are just handled as textBaseline = top or bottom.
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textbaseline-hanging
|
||||
// Default baseline of draw_text is top so do nothing by CanvasTextBaseline::Top and CanvasTextBasline::Hanging
|
||||
if (drawing_state.text_baseline == Bindings::CanvasTextBaseline::Middle) {
|
||||
transform = Gfx::AffineTransform {}.set_translation({ 0, font->pixel_size() / 2 }).multiply(transform);
|
||||
}
|
||||
if (drawing_state.text_baseline == Bindings::CanvasTextBaseline::Top || drawing_state.text_baseline == Bindings::CanvasTextBaseline::Hanging) {
|
||||
transform = Gfx::AffineTransform {}.set_translation({ 0, font->pixel_size() }).multiply(transform);
|
||||
}
|
||||
|
||||
return path.copy_transformed(transform);
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::fill_text(StringView text, float x, float y, Optional<double> max_width)
|
||||
{
|
||||
fill_internal(text_path(text, x, y, max_width), Gfx::WindingRule::Nonzero);
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::stroke_text(StringView text, float x, float y, Optional<double> max_width)
|
||||
{
|
||||
stroke_internal(text_path(text, x, y, max_width));
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::begin_path()
|
||||
{
|
||||
path().clear();
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path)
|
||||
{
|
||||
auto* painter = this->painter();
|
||||
if (!painter)
|
||||
return;
|
||||
|
||||
paint_shadow_for_stroke_internal(path);
|
||||
|
||||
auto& state = drawing_state();
|
||||
|
||||
// FIXME: Honor state's line_cap, line_join, miter_limit, dash_list, and line_dash_offset.
|
||||
|
||||
if (auto color = state.stroke_style.as_color(); color.has_value()) {
|
||||
painter->stroke_path(path, color->with_opacity(state.global_alpha), state.line_width);
|
||||
} else {
|
||||
painter->stroke_path(path, state.stroke_style.to_gfx_paint_style(), state.line_width, state.global_alpha);
|
||||
}
|
||||
|
||||
did_draw(path.bounding_box());
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::stroke()
|
||||
{
|
||||
stroke_internal(path());
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::stroke(Path2D const& path)
|
||||
{
|
||||
stroke_internal(path.path());
|
||||
}
|
||||
|
||||
static Gfx::WindingRule parse_fill_rule(StringView fill_rule)
|
||||
{
|
||||
if (fill_rule == "evenodd"sv)
|
||||
return Gfx::WindingRule::EvenOdd;
|
||||
if (fill_rule == "nonzero"sv)
|
||||
return Gfx::WindingRule::Nonzero;
|
||||
dbgln("Unrecognized fillRule for CRC2D.fill() - this problem goes away once we pass an enum instead of a string");
|
||||
return Gfx::WindingRule::Nonzero;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::fill_internal(Gfx::Path const& path, Gfx::WindingRule winding_rule)
|
||||
{
|
||||
auto* painter = this->painter();
|
||||
if (!painter)
|
||||
return;
|
||||
|
||||
paint_shadow_for_fill_internal(path, winding_rule);
|
||||
|
||||
auto path_to_fill = path;
|
||||
path_to_fill.close_all_subpaths();
|
||||
auto& state = this->drawing_state();
|
||||
if (auto color = state.fill_style.as_color(); color.has_value()) {
|
||||
painter->fill_path(path_to_fill, color->with_opacity(state.global_alpha), winding_rule);
|
||||
} else {
|
||||
painter->fill_path(path_to_fill, state.fill_style.to_gfx_paint_style(), state.global_alpha, winding_rule);
|
||||
}
|
||||
|
||||
did_draw(path_to_fill.bounding_box());
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::fill(StringView fill_rule)
|
||||
{
|
||||
fill_internal(path(), parse_fill_rule(fill_rule));
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::fill(Path2D& path, StringView fill_rule)
|
||||
{
|
||||
fill_internal(path.path(), parse_fill_rule(fill_rule));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createimagedata
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> CanvasRenderingContext2D::create_image_data(int width, int height, Optional<ImageDataSettings> const& settings) const
|
||||
{
|
||||
// 1. If one or both of sw and sh are zero, then throw an "IndexSizeError" DOMException.
|
||||
if (width == 0 || height == 0)
|
||||
return WebIDL::IndexSizeError::create(realm(), "Width and height must not be zero"_string);
|
||||
|
||||
int abs_width = abs(width);
|
||||
int abs_height = abs(height);
|
||||
|
||||
// 2. Let newImageData be a new ImageData object.
|
||||
// 3. Initialize newImageData given the absolute magnitude of sw, the absolute magnitude of sh, settings set to settings, and defaultColorSpace set to this's color space.
|
||||
auto image_data = TRY(ImageData::create(realm(), abs_width, abs_height, settings));
|
||||
|
||||
// 4. Initialize the image data of newImageData to transparent black.
|
||||
// ... this is handled by ImageData::create()
|
||||
|
||||
// 5. Return newImageData.
|
||||
return image_data;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createimagedata-imagedata
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> CanvasRenderingContext2D::create_image_data(ImageData const& image_data) const
|
||||
{
|
||||
// 1. Let newImageData be a new ImageData object.
|
||||
// 2. Initialize newImageData given the value of imagedata's width attribute, the value of imagedata's height attribute, and defaultColorSpace set to the value of imagedata's colorSpace attribute.
|
||||
// FIXME: Set defaultColorSpace to the value of image_data's colorSpace attribute
|
||||
// 3. Initialize the image data of newImageData to transparent black.
|
||||
// NOTE: No-op, already done during creation.
|
||||
// 4. Return newImageData.
|
||||
return TRY(ImageData::create(realm(), image_data.width(), image_data.height()));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-getimagedata
|
||||
WebIDL::ExceptionOr<JS::GCPtr<ImageData>> CanvasRenderingContext2D::get_image_data(int x, int y, int width, int height, Optional<ImageDataSettings> const& settings) const
|
||||
{
|
||||
// 1. If either the sw or sh arguments are zero, then throw an "IndexSizeError" DOMException.
|
||||
if (width == 0 || height == 0)
|
||||
return WebIDL::IndexSizeError::create(realm(), "Width and height must not be zero"_string);
|
||||
|
||||
// 2. If the CanvasRenderingContext2D's origin-clean flag is set to false, then throw a "SecurityError" DOMException.
|
||||
if (!m_origin_clean)
|
||||
return WebIDL::SecurityError::create(realm(), "CanvasRenderingContext2D is not origin-clean"_string);
|
||||
|
||||
// ImageData initialization requires positive width and height
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#initialize-an-imagedata-object
|
||||
int abs_width = abs(width);
|
||||
int abs_height = abs(height);
|
||||
|
||||
// 3. Let imageData be a new ImageData object.
|
||||
// 4. Initialize imageData given sw, sh, settings set to settings, and defaultColorSpace set to this's color space.
|
||||
auto image_data = TRY(ImageData::create(realm(), abs_width, abs_height, settings));
|
||||
|
||||
// NOTE: We don't attempt to create the underlying bitmap here; if it doesn't exist, it's like copying only transparent black pixels (which is a no-op).
|
||||
if (!canvas_element().surface())
|
||||
return image_data;
|
||||
auto const snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*canvas_element().surface());
|
||||
|
||||
// 5. Let the source rectangle be the rectangle whose corners are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).
|
||||
auto source_rect = Gfx::Rect { x, y, abs_width, abs_height };
|
||||
|
||||
// NOTE: The spec doesn't seem to define this behavior, but MDN does and the WPT tests
|
||||
// assume it works this way.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData#sw
|
||||
if (width < 0 || height < 0) {
|
||||
source_rect = source_rect.translated(min(width, 0), min(height, 0));
|
||||
}
|
||||
auto source_rect_intersected = source_rect.intersected(snapshot->rect());
|
||||
|
||||
// 6. Set the pixel values of imageData to be the pixels of this's output bitmap in the area specified by the source rectangle in the bitmap's coordinate space units, converted from this's color space to imageData's colorSpace using 'relative-colorimetric' rendering intent.
|
||||
// NOTE: Internally we must use premultiplied alpha, but ImageData should hold unpremultiplied alpha. This conversion
|
||||
// might result in a loss of precision, but is according to spec.
|
||||
// See: https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context
|
||||
ASSERT(snapshot->alpha_type() == Gfx::AlphaType::Premultiplied);
|
||||
ASSERT(image_data->bitmap().alpha_type() == Gfx::AlphaType::Unpremultiplied);
|
||||
|
||||
auto painter = Gfx::Painter::create(image_data->bitmap());
|
||||
painter->draw_bitmap(image_data->bitmap().rect().to_type<float>(), *snapshot, source_rect_intersected, Gfx::ScalingMode::NearestNeighbor, drawing_state().global_alpha);
|
||||
|
||||
// 7. Set the pixels values of imageData for areas of the source rectangle that are outside of the output bitmap to transparent black.
|
||||
// NOTE: No-op, already done during creation.
|
||||
|
||||
// 8. Return imageData.
|
||||
return image_data;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::put_image_data(ImageData const& image_data, float x, float y)
|
||||
{
|
||||
if (auto* painter = this->painter()) {
|
||||
auto dst_rect = Gfx::FloatRect(x, y, image_data.width(), image_data.height());
|
||||
painter->draw_bitmap(dst_rect, Gfx::ImmutableBitmap::create(image_data.bitmap()), image_data.bitmap().rect(), Gfx::ScalingMode::NearestNeighbor, 1.0f);
|
||||
did_draw(dst_rect);
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#reset-the-rendering-context-to-its-default-state
|
||||
void CanvasRenderingContext2D::reset_to_default_state()
|
||||
{
|
||||
auto surface = canvas_element().surface();
|
||||
|
||||
// 1. Clear canvas's bitmap to transparent black.
|
||||
if (surface) {
|
||||
painter()->clear_rect(surface->rect().to_type<float>(), Color::Transparent);
|
||||
}
|
||||
|
||||
// 2. Empty the list of subpaths in context's current default path.
|
||||
path().clear();
|
||||
|
||||
// 3. Clear the context's drawing state stack.
|
||||
clear_drawing_state_stack();
|
||||
|
||||
// 4. Reset everything that drawing state consists of to their initial values.
|
||||
reset_drawing_state();
|
||||
|
||||
if (surface)
|
||||
did_draw(surface->rect().to_type<float>());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-measuretext
|
||||
JS::NonnullGCPtr<TextMetrics> CanvasRenderingContext2D::measure_text(StringView text)
|
||||
{
|
||||
// The measureText(text) method steps are to run the text preparation
|
||||
// algorithm, passing it text and the object implementing the CanvasText
|
||||
// interface, and then using the returned inline box must return a new
|
||||
// TextMetrics object with members behaving as described in the following
|
||||
// list:
|
||||
auto prepared_text = prepare_text(text);
|
||||
auto metrics = TextMetrics::create(realm());
|
||||
// FIXME: Use the font that was used to create the glyphs in prepared_text.
|
||||
auto font = current_font();
|
||||
|
||||
// width attribute: The width of that inline box, in CSS pixels. (The text's advance width.)
|
||||
metrics->set_width(prepared_text.bounding_box.width());
|
||||
// actualBoundingBoxLeft attribute: The distance parallel to the baseline from the alignment point given by the textAlign attribute to the left side of the bounding rectangle of the given text, in CSS pixels; positive numbers indicating a distance going left from the given alignment point.
|
||||
metrics->set_actual_bounding_box_left(-prepared_text.bounding_box.left());
|
||||
// actualBoundingBoxRight attribute: The distance parallel to the baseline from the alignment point given by the textAlign attribute to the right side of the bounding rectangle of the given text, in CSS pixels; positive numbers indicating a distance going right from the given alignment point.
|
||||
metrics->set_actual_bounding_box_right(prepared_text.bounding_box.right());
|
||||
// fontBoundingBoxAscent attribute: The distance from the horizontal line indicated by the textBaseline attribute to the ascent metric of the first available font, in CSS pixels; positive numbers indicating a distance going up from the given baseline.
|
||||
metrics->set_font_bounding_box_ascent(font->baseline());
|
||||
// fontBoundingBoxDescent attribute: The distance from the horizontal line indicated by the textBaseline attribute to the descent metric of the first available font, in CSS pixels; positive numbers indicating a distance going down from the given baseline.
|
||||
metrics->set_font_bounding_box_descent(prepared_text.bounding_box.height() - font->baseline());
|
||||
// actualBoundingBoxAscent attribute: The distance from the horizontal line indicated by the textBaseline attribute to the top of the bounding rectangle of the given text, in CSS pixels; positive numbers indicating a distance going up from the given baseline.
|
||||
metrics->set_actual_bounding_box_ascent(font->baseline());
|
||||
// actualBoundingBoxDescent attribute: The distance from the horizontal line indicated by the textBaseline attribute to the bottom of the bounding rectangle of the given text, in CSS pixels; positive numbers indicating a distance going down from the given baseline.
|
||||
metrics->set_actual_bounding_box_descent(prepared_text.bounding_box.height() - font->baseline());
|
||||
// emHeightAscent attribute: The distance from the horizontal line indicated by the textBaseline attribute to the highest top of the em squares in the inline box, in CSS pixels; positive numbers indicating that the given baseline is below the top of that em square (so this value will usually be positive). Zero if the given baseline is the top of that em square; half the font size if the given baseline is the middle of that em square.
|
||||
metrics->set_em_height_ascent(font->baseline());
|
||||
// emHeightDescent attribute: The distance from the horizontal line indicated by the textBaseline attribute to the lowest bottom of the em squares in the inline box, in CSS pixels; positive numbers indicating that the given baseline is above the bottom of that em square. (Zero if the given baseline is the bottom of that em square.)
|
||||
metrics->set_em_height_descent(prepared_text.bounding_box.height() - font->baseline());
|
||||
// hangingBaseline attribute: The distance from the horizontal line indicated by the textBaseline attribute to the hanging baseline of the inline box, in CSS pixels; positive numbers indicating that the given baseline is below the hanging baseline. (Zero if the given baseline is the hanging baseline.)
|
||||
metrics->set_hanging_baseline(font->baseline());
|
||||
// alphabeticBaseline attribute: The distance from the horizontal line indicated by the textBaseline attribute to the alphabetic baseline of the inline box, in CSS pixels; positive numbers indicating that the given baseline is below the alphabetic baseline. (Zero if the given baseline is the alphabetic baseline.)
|
||||
metrics->set_font_bounding_box_ascent(0);
|
||||
// ideographicBaseline attribute: The distance from the horizontal line indicated by the textBaseline attribute to the ideographic-under baseline of the inline box, in CSS pixels; positive numbers indicating that the given baseline is below the ideographic-under baseline. (Zero if the given baseline is the ideographic-under baseline.)
|
||||
metrics->set_font_bounding_box_ascent(0);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
RefPtr<Gfx::Font const> CanvasRenderingContext2D::current_font()
|
||||
{
|
||||
// When font style value is empty load default font
|
||||
if (!drawing_state().font_style_value) {
|
||||
set_font("10px sans-serif"sv);
|
||||
}
|
||||
|
||||
// Get current loaded font
|
||||
return drawing_state().current_font;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#text-preparation-algorithm
|
||||
CanvasRenderingContext2D::PreparedText CanvasRenderingContext2D::prepare_text(ByteString const& text, float max_width)
|
||||
{
|
||||
// 1. If maxWidth was provided but is less than or equal to zero or equal to NaN, then return an empty array.
|
||||
if (max_width <= 0 || max_width != max_width) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// 2. Replace all ASCII whitespace in text with U+0020 SPACE characters.
|
||||
StringBuilder builder { text.length() };
|
||||
for (auto c : text) {
|
||||
builder.append(Infra::is_ascii_whitespace(c) ? ' ' : c);
|
||||
}
|
||||
auto replaced_text = MUST(builder.to_string());
|
||||
|
||||
// 3. Let font be the current font of target, as given by that object's font attribute.
|
||||
auto font = current_font();
|
||||
|
||||
// 4. Apply the appropriate step from the following list to determine the value of direction:
|
||||
// 4.1. If the target object's direction attribute has the value "ltr": Let direction be 'ltr'.
|
||||
// 4.2. If the target object's direction attribute has the value "rtl": Let direction be 'rtl'.
|
||||
// 4.3. If the target object's font style source object is an element: Let direction be the directionality of the target object's font style source object.
|
||||
// 4.4. If the target object's font style source object is a Document with a non-null document element: Let direction be the directionality of the target object's font style source object's document element.
|
||||
// 4.5. Otherwise: Let direction be 'ltr'.
|
||||
// FIXME: Once we have CanvasTextDrawingStyles, implement directionality.
|
||||
|
||||
// 5. Form a hypothetical infinitely-wide CSS line box containing a single inline box containing the text text, with its CSS properties set as follows:
|
||||
// 'direction' -> direction
|
||||
// 'font' -> font
|
||||
// 'font-kerning' -> target's fontKerning
|
||||
// 'font-stretch' -> target's fontStretch
|
||||
// 'font-variant-caps' -> target's fontVariantCaps
|
||||
// 'letter-spacing' -> target's letterSpacing
|
||||
// SVG text-rendering -> target's textRendering
|
||||
// 'white-space' -> 'pre'
|
||||
// 'word-spacing' -> target's wordSpacing
|
||||
// ...and with all other properties set to their initial values.
|
||||
// FIXME: Actually use a LineBox here instead of, you know, using the default font and measuring its size (which is not the spec at all).
|
||||
// FIXME: Once we have CanvasTextDrawingStyles, add the CSS attributes.
|
||||
size_t height = font->pixel_size();
|
||||
|
||||
// 6. If maxWidth was provided and the hypothetical width of the inline box in the hypothetical line box is greater than maxWidth CSS pixels, then change font to have a more condensed font (if one is available or if a reasonably readable one can be synthesized by applying a horizontal scale factor to the font) or a smaller font, and return to the previous step.
|
||||
// FIXME: Record the font size used for this piece of text, and actually retry with a smaller size if needed.
|
||||
|
||||
// 7. The anchor point is a point on the inline box, and the physical alignment is one of the values left, right, and center. These variables are determined by the textAlign and textBaseline values as follows:
|
||||
// Horizontal position:
|
||||
// 7.1. If textAlign is left, if textAlign is start and direction is 'ltr' or if textAlign is end and direction is 'rtl': Let the anchor point's horizontal position be the left edge of the inline box, and let physical alignment be left.
|
||||
// 7.2. If textAlign is right, if textAlign is end and direction is 'ltr' or if textAlign is start and direction is 'rtl': Let the anchor point's horizontal position be the right edge of the inline box, and let physical alignment be right.
|
||||
// 7.3. If textAlign is center: Let the anchor point's horizontal position be half way between the left and right edges of the inline box, and let physical alignment be center.
|
||||
// Vertical position:
|
||||
// 7.4. If textBaseline is top: Let the anchor point's vertical position be the top of the em box of the first available font of the inline box.
|
||||
// 7.5. If textBaseline is hanging: Let the anchor point's vertical position be the hanging baseline of the first available font of the inline box.
|
||||
// 7.6. If textBaseline is middle: Let the anchor point's vertical position be half way between the bottom and the top of the em box of the first available font of the inline box.
|
||||
// 7.7. If textBaseline is alphabetic: Let the anchor point's vertical position be the alphabetic baseline of the first available font of the inline box.
|
||||
// 7.8. If textBaseline is ideographic: Let the anchor point's vertical position be the ideographic-under baseline of the first available font of the inline box.
|
||||
// 7.9. If textBaseline is bottom: Let the anchor point's vertical position be the bottom of the em box of the first available font of the inline box.
|
||||
// FIXME: Once we have CanvasTextDrawingStyles, handle the alignment and baseline.
|
||||
Gfx::FloatPoint anchor { 0, 0 };
|
||||
auto physical_alignment = Gfx::TextAlignment::CenterLeft;
|
||||
|
||||
auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr);
|
||||
|
||||
// 8. Let result be an array constructed by iterating over each glyph in the inline box from left to right (if any), adding to the array, for each glyph, the shape of the glyph as it is in the inline box, positioned on a coordinate space using CSS pixels with its origin is at the anchor point.
|
||||
PreparedText prepared_text { glyph_run, physical_alignment, { 0, 0, static_cast<int>(glyph_run->width()), static_cast<int>(height) } };
|
||||
|
||||
// 9. Return result, physical alignment, and the inline box.
|
||||
return prepared_text;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::clip_internal(Gfx::Path& path, Gfx::WindingRule winding_rule)
|
||||
{
|
||||
auto* painter = this->painter();
|
||||
if (!painter)
|
||||
return;
|
||||
|
||||
path.close_all_subpaths();
|
||||
painter->clip(path, winding_rule);
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::clip(StringView fill_rule)
|
||||
{
|
||||
clip_internal(path(), parse_fill_rule(fill_rule));
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::clip(Path2D& path, StringView fill_rule)
|
||||
{
|
||||
clip_internal(path.path(), parse_fill_rule(fill_rule));
|
||||
}
|
||||
|
||||
static bool is_point_in_path_internal(Gfx::Path path, double x, double y, StringView fill_rule)
|
||||
{
|
||||
return path.contains(Gfx::FloatPoint(x, y), parse_fill_rule(fill_rule));
|
||||
}
|
||||
|
||||
bool CanvasRenderingContext2D::is_point_in_path(double x, double y, StringView fill_rule)
|
||||
{
|
||||
return is_point_in_path_internal(path(), x, y, fill_rule);
|
||||
}
|
||||
|
||||
bool CanvasRenderingContext2D::is_point_in_path(Path2D const& path, double x, double y, StringView fill_rule)
|
||||
{
|
||||
return is_point_in_path_internal(path.path(), x, y, fill_rule);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#check-the-usability-of-the-image-argument
|
||||
WebIDL::ExceptionOr<CanvasImageSourceUsability> check_usability_of_image(CanvasImageSource const& image)
|
||||
{
|
||||
// 1. Switch on image:
|
||||
auto usability = TRY(image.visit(
|
||||
// HTMLOrSVGImageElement
|
||||
[](JS::Handle<HTMLImageElement> const& image_element) -> WebIDL::ExceptionOr<Optional<CanvasImageSourceUsability>> {
|
||||
// FIXME: If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
|
||||
|
||||
// If image is not fully decodable, then return bad.
|
||||
if (!image_element->immutable_bitmap())
|
||||
return { CanvasImageSourceUsability::Bad };
|
||||
|
||||
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
|
||||
if (image_element->immutable_bitmap()->width() == 0 || image_element->immutable_bitmap()->height() == 0)
|
||||
return { CanvasImageSourceUsability::Bad };
|
||||
return Optional<CanvasImageSourceUsability> {};
|
||||
},
|
||||
// FIXME: Don't duplicate this for HTMLImageElement and SVGImageElement.
|
||||
[](JS::Handle<SVG::SVGImageElement> const& image_element) -> WebIDL::ExceptionOr<Optional<CanvasImageSourceUsability>> {
|
||||
// FIXME: If image's current request's state is broken, then throw an "InvalidStateError" DOMException.
|
||||
|
||||
// If image is not fully decodable, then return bad.
|
||||
if (!image_element->current_image_bitmap())
|
||||
return { CanvasImageSourceUsability::Bad };
|
||||
|
||||
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
|
||||
if (image_element->current_image_bitmap()->width() == 0 || image_element->current_image_bitmap()->height() == 0)
|
||||
return { CanvasImageSourceUsability::Bad };
|
||||
return Optional<CanvasImageSourceUsability> {};
|
||||
},
|
||||
|
||||
[](JS::Handle<HTML::HTMLVideoElement> const& video_element) -> WebIDL::ExceptionOr<Optional<CanvasImageSourceUsability>> {
|
||||
// If image's readyState attribute is either HAVE_NOTHING or HAVE_METADATA, then return bad.
|
||||
if (video_element->ready_state() == HTML::HTMLMediaElement::ReadyState::HaveNothing || video_element->ready_state() == HTML::HTMLMediaElement::ReadyState::HaveMetadata) {
|
||||
return { CanvasImageSourceUsability::Bad };
|
||||
}
|
||||
return Optional<CanvasImageSourceUsability> {};
|
||||
},
|
||||
|
||||
// HTMLCanvasElement
|
||||
// FIXME: OffscreenCanvas
|
||||
[](JS::Handle<HTMLCanvasElement> const& canvas_element) -> WebIDL::ExceptionOr<Optional<CanvasImageSourceUsability>> {
|
||||
// If image has either a horizontal dimension or a vertical dimension equal to zero, then throw an "InvalidStateError" DOMException.
|
||||
if (canvas_element->width() == 0 || canvas_element->height() == 0)
|
||||
return WebIDL::InvalidStateError::create(canvas_element->realm(), "Canvas width or height is zero"_string);
|
||||
return Optional<CanvasImageSourceUsability> {};
|
||||
},
|
||||
|
||||
// ImageBitmap
|
||||
// FIXME: VideoFrame
|
||||
[](JS::Handle<ImageBitmap> const& image_bitmap) -> WebIDL::ExceptionOr<Optional<CanvasImageSourceUsability>> {
|
||||
if (image_bitmap->is_detached())
|
||||
return WebIDL::InvalidStateError::create(image_bitmap->realm(), "Image bitmap is detached"_string);
|
||||
return Optional<CanvasImageSourceUsability> {};
|
||||
}));
|
||||
if (usability.has_value())
|
||||
return usability.release_value();
|
||||
|
||||
// 2. Return good.
|
||||
return { CanvasImageSourceUsability::Good };
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#the-image-argument-is-not-origin-clean
|
||||
bool image_is_not_origin_clean(CanvasImageSource const& image)
|
||||
{
|
||||
// An object image is not origin-clean if, switching on image's type:
|
||||
return image.visit(
|
||||
// HTMLOrSVGImageElement
|
||||
[](JS::Handle<HTMLImageElement> const&) {
|
||||
// FIXME: image's current request's image data is CORS-cross-origin.
|
||||
return false;
|
||||
},
|
||||
[](JS::Handle<SVG::SVGImageElement> const&) {
|
||||
// FIXME: image's current request's image data is CORS-cross-origin.
|
||||
return false;
|
||||
},
|
||||
[](JS::Handle<HTML::HTMLVideoElement> const&) {
|
||||
// FIXME: image's media data is CORS-cross-origin.
|
||||
return false;
|
||||
},
|
||||
// HTMLCanvasElement
|
||||
[](OneOf<JS::Handle<HTMLCanvasElement>, JS::Handle<ImageBitmap>> auto const&) {
|
||||
// FIXME: image's bitmap's origin-clean flag is false.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
bool CanvasRenderingContext2D::image_smoothing_enabled() const
|
||||
{
|
||||
return drawing_state().image_smoothing_enabled;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::set_image_smoothing_enabled(bool enabled)
|
||||
{
|
||||
drawing_state().image_smoothing_enabled = enabled;
|
||||
}
|
||||
|
||||
Bindings::ImageSmoothingQuality CanvasRenderingContext2D::image_smoothing_quality() const
|
||||
{
|
||||
return drawing_state().image_smoothing_quality;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::set_image_smoothing_quality(Bindings::ImageSmoothingQuality quality)
|
||||
{
|
||||
drawing_state().image_smoothing_quality = quality;
|
||||
}
|
||||
|
||||
float CanvasRenderingContext2D::global_alpha() const
|
||||
{
|
||||
return drawing_state().global_alpha;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-globalalpha
|
||||
void CanvasRenderingContext2D::set_global_alpha(float alpha)
|
||||
{
|
||||
// 1. If the given value is either infinite, NaN, or not in the range 0.0 to 1.0, then return.
|
||||
if (!isfinite(alpha) || alpha < 0.0f || alpha > 1.0f) {
|
||||
return;
|
||||
}
|
||||
// 2. Otherwise, set this's global alpha to the given value.
|
||||
drawing_state().global_alpha = alpha;
|
||||
}
|
||||
|
||||
float CanvasRenderingContext2D::shadow_offset_x() const
|
||||
{
|
||||
return drawing_state().shadow_offset_x;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::set_shadow_offset_x(float offsetX)
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowoffsetx
|
||||
drawing_state().shadow_offset_x = offsetX;
|
||||
}
|
||||
|
||||
float CanvasRenderingContext2D::shadow_offset_y() const
|
||||
{
|
||||
return drawing_state().shadow_offset_y;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::set_shadow_offset_y(float offsetY)
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowoffsety
|
||||
drawing_state().shadow_offset_y = offsetY;
|
||||
}
|
||||
|
||||
String CanvasRenderingContext2D::shadow_color() const
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowcolor
|
||||
return drawing_state().shadow_color.to_string(Gfx::Color::HTMLCompatibleSerialization::Yes);
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::set_shadow_color(String color)
|
||||
{
|
||||
auto& realm = static_cast<CanvasRenderingContext2D&>(*this).realm();
|
||||
|
||||
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
|
||||
auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), color);
|
||||
|
||||
auto style_value = parser.parse_as_css_value(CSS::PropertyID::Color);
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
|
||||
if (style_value && style_value->has_color()) {
|
||||
auto parsedValue = style_value->to_color(OptionalNone());
|
||||
|
||||
// 4. Set this's shadow color to parsedValue.
|
||||
drawing_state().shadow_color = parsedValue;
|
||||
} else {
|
||||
// 3. If parsedValue is failure, then return.
|
||||
return;
|
||||
}
|
||||
}
|
||||
void CanvasRenderingContext2D::paint_shadow_for_fill_internal(Gfx::Path const& path, Gfx::WindingRule winding_rule)
|
||||
{
|
||||
auto* painter = this->painter();
|
||||
if (!painter)
|
||||
return;
|
||||
|
||||
auto path_to_fill = path;
|
||||
path_to_fill.close_all_subpaths();
|
||||
|
||||
auto& state = this->drawing_state();
|
||||
|
||||
painter->save();
|
||||
|
||||
Gfx::AffineTransform transform;
|
||||
transform.translate(state.shadow_offset_x, state.shadow_offset_y);
|
||||
painter->set_transform(transform);
|
||||
painter->fill_path(path_to_fill, state.shadow_color.with_opacity(state.global_alpha), winding_rule);
|
||||
|
||||
painter->restore();
|
||||
|
||||
did_draw(path_to_fill.bounding_box());
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::paint_shadow_for_stroke_internal(Gfx::Path const& path)
|
||||
{
|
||||
auto* painter = this->painter();
|
||||
if (!painter)
|
||||
return;
|
||||
|
||||
auto& state = drawing_state();
|
||||
|
||||
painter->save();
|
||||
|
||||
Gfx::AffineTransform transform;
|
||||
transform.translate(state.shadow_offset_x, state.shadow_offset_y);
|
||||
painter->set_transform(transform);
|
||||
painter->stroke_path(path, state.shadow_color.with_opacity(state.global_alpha), state.line_width);
|
||||
|
||||
painter->restore();
|
||||
|
||||
did_draw(path.bounding_box());
|
||||
}
|
||||
|
||||
}
|
161
Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
Normal file
161
Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibGfx/AffineTransform.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/Path.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasCompositing.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasDrawPath.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasFillStrokeStyles.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasImageData.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasImageSmoothing.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasPath.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasPathDrawingStyles.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasRect.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasShadowStyles.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasText.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasTransform.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Layout/LineBox.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class CanvasRenderingContext2D
|
||||
: public Bindings::PlatformObject
|
||||
, public CanvasPath
|
||||
, public CanvasState
|
||||
, public CanvasTransform<CanvasRenderingContext2D>
|
||||
, public CanvasFillStrokeStyles<CanvasRenderingContext2D>
|
||||
, public CanvasShadowStyles<CanvasRenderingContext2D>
|
||||
, public CanvasRect
|
||||
, public CanvasDrawPath
|
||||
, public CanvasText
|
||||
, public CanvasDrawImage
|
||||
, public CanvasImageData
|
||||
, public CanvasImageSmoothing
|
||||
, public CanvasCompositing
|
||||
, public CanvasPathDrawingStyles<CanvasRenderingContext2D>
|
||||
, public CanvasTextDrawingStyles<CanvasRenderingContext2D> {
|
||||
|
||||
WEB_PLATFORM_OBJECT(CanvasRenderingContext2D, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(CanvasRenderingContext2D);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static JS::NonnullGCPtr<CanvasRenderingContext2D> create(JS::Realm&, HTMLCanvasElement&);
|
||||
virtual ~CanvasRenderingContext2D() override;
|
||||
|
||||
virtual void fill_rect(float x, float y, float width, float height) override;
|
||||
virtual void stroke_rect(float x, float y, float width, float height) override;
|
||||
virtual void clear_rect(float x, float y, float width, float height) override;
|
||||
|
||||
virtual WebIDL::ExceptionOr<void> draw_image_internal(CanvasImageSource const&, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height) override;
|
||||
|
||||
virtual void begin_path() override;
|
||||
virtual void stroke() override;
|
||||
virtual void stroke(Path2D const& path) override;
|
||||
|
||||
virtual void fill_text(StringView, float x, float y, Optional<double> max_width) override;
|
||||
virtual void stroke_text(StringView, float x, float y, Optional<double> max_width) override;
|
||||
|
||||
virtual void fill(StringView fill_rule) override;
|
||||
virtual void fill(Path2D& path, StringView fill_rule) override;
|
||||
|
||||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> create_image_data(int width, int height, Optional<ImageDataSettings> const& settings = {}) const override;
|
||||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> create_image_data(ImageData const& image_data) const override;
|
||||
virtual WebIDL::ExceptionOr<JS::GCPtr<ImageData>> get_image_data(int x, int y, int width, int height, Optional<ImageDataSettings> const& settings = {}) const override;
|
||||
virtual void put_image_data(ImageData const&, float x, float y) override;
|
||||
|
||||
virtual void reset_to_default_state() override;
|
||||
|
||||
JS::NonnullGCPtr<HTMLCanvasElement> canvas_for_binding() const;
|
||||
|
||||
virtual JS::NonnullGCPtr<TextMetrics> measure_text(StringView text) override;
|
||||
|
||||
virtual void clip(StringView fill_rule) override;
|
||||
virtual void clip(Path2D& path, StringView fill_rule) override;
|
||||
|
||||
virtual bool is_point_in_path(double x, double y, StringView fill_rule) override;
|
||||
virtual bool is_point_in_path(Path2D const& path, double x, double y, StringView fill_rule) override;
|
||||
|
||||
virtual bool image_smoothing_enabled() const override;
|
||||
virtual void set_image_smoothing_enabled(bool) override;
|
||||
virtual Bindings::ImageSmoothingQuality image_smoothing_quality() const override;
|
||||
virtual void set_image_smoothing_quality(Bindings::ImageSmoothingQuality) override;
|
||||
|
||||
virtual float global_alpha() const override;
|
||||
virtual void set_global_alpha(float) override;
|
||||
|
||||
virtual float shadow_offset_x() const override;
|
||||
virtual void set_shadow_offset_x(float) override;
|
||||
virtual float shadow_offset_y() const override;
|
||||
virtual void set_shadow_offset_y(float) override;
|
||||
virtual String shadow_color() const override;
|
||||
virtual void set_shadow_color(String) override;
|
||||
|
||||
HTMLCanvasElement& canvas_element();
|
||||
HTMLCanvasElement const& canvas_element() const;
|
||||
|
||||
[[nodiscard]] Gfx::Painter* painter();
|
||||
|
||||
private:
|
||||
explicit CanvasRenderingContext2D(JS::Realm&, HTMLCanvasElement&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
virtual Gfx::Painter* painter_for_canvas_state() override { return painter(); }
|
||||
virtual Gfx::Path& path_for_canvas_state() override { return path(); }
|
||||
|
||||
struct PreparedText {
|
||||
RefPtr<Gfx::GlyphRun> glyph_run;
|
||||
Gfx::TextAlignment physical_alignment;
|
||||
Gfx::IntRect bounding_box;
|
||||
};
|
||||
|
||||
void did_draw(Gfx::FloatRect const&);
|
||||
|
||||
RefPtr<Gfx::Font const> current_font();
|
||||
|
||||
PreparedText prepare_text(ByteString const& text, float max_width = INFINITY);
|
||||
|
||||
[[nodiscard]] Gfx::Path rect_path(float x, float y, float width, float height);
|
||||
[[nodiscard]] Gfx::Path text_path(StringView text, float x, float y, Optional<double> max_width);
|
||||
|
||||
void stroke_internal(Gfx::Path const&);
|
||||
void fill_internal(Gfx::Path const&, Gfx::WindingRule);
|
||||
void clip_internal(Gfx::Path&, Gfx::WindingRule);
|
||||
void paint_shadow_for_fill_internal(Gfx::Path const&, Gfx::WindingRule);
|
||||
void paint_shadow_for_stroke_internal(Gfx::Path const&);
|
||||
|
||||
JS::NonnullGCPtr<HTMLCanvasElement> m_element;
|
||||
OwnPtr<Gfx::Painter> m_painter;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean
|
||||
bool m_origin_clean { true };
|
||||
};
|
||||
|
||||
enum class CanvasImageSourceUsability {
|
||||
Bad,
|
||||
Good,
|
||||
};
|
||||
|
||||
WebIDL::ExceptionOr<CanvasImageSourceUsability> check_usability_of_image(CanvasImageSource const&);
|
||||
bool image_is_not_origin_clean(CanvasImageSource const&);
|
||||
|
||||
}
|
61
Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
Normal file
61
Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl
Normal file
|
@ -0,0 +1,61 @@
|
|||
#import <HTML/HTMLCanvasElement.idl>
|
||||
#import <HTML/Canvas/CanvasCompositing.idl>
|
||||
#import <HTML/Canvas/CanvasDrawImage.idl>
|
||||
#import <HTML/Canvas/CanvasDrawPath.idl>
|
||||
#import <HTML/Canvas/CanvasFillStrokeStyles.idl>
|
||||
#import <HTML/Canvas/CanvasFilters.idl>
|
||||
#import <HTML/Canvas/CanvasImageData.idl>
|
||||
#import <HTML/Canvas/CanvasImageSmoothing.idl>
|
||||
#import <HTML/Canvas/CanvasPath.idl>
|
||||
#import <HTML/Canvas/CanvasPathDrawingStyles.idl>
|
||||
#import <HTML/Canvas/CanvasTextDrawingStyles.idl>
|
||||
#import <HTML/Canvas/CanvasRect.idl>
|
||||
#import <HTML/Canvas/CanvasShadowStyles.idl>
|
||||
#import <HTML/Canvas/CanvasState.idl>
|
||||
#import <HTML/Canvas/CanvasText.idl>
|
||||
#import <HTML/Canvas/CanvasTransform.idl>
|
||||
#import <HTML/Canvas/CanvasUserInterface.idl>
|
||||
|
||||
enum PredefinedColorSpace { "srgb", "display-p3" };
|
||||
|
||||
dictionary CanvasRenderingContext2DSettings {
|
||||
boolean alpha = true;
|
||||
boolean desynchronized = false;
|
||||
PredefinedColorSpace colorSpace = "srgb";
|
||||
boolean willReadFrequently = false;
|
||||
};
|
||||
|
||||
enum ImageSmoothingQuality { "low", "medium", "high" };
|
||||
|
||||
// FIXME: This should be in CanvasPathDrawingStyles.idl but then it is not exported
|
||||
enum CanvasLineCap { "butt", "round", "square" };
|
||||
enum CanvasLineJoin { "round", "bevel", "miter" };
|
||||
|
||||
// FIXME: This should be in CanvasTextDrawingStyles.idl but then it is not exported
|
||||
enum CanvasTextAlign { "start", "end", "left", "right", "center" };
|
||||
enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasrenderingcontext2d
|
||||
[Exposed=Window]
|
||||
interface CanvasRenderingContext2D {
|
||||
[ImplementedAs=canvas_for_binding] readonly attribute HTMLCanvasElement canvas;
|
||||
|
||||
[FIXME] CanvasRenderingContext2DSettings getContextAttributes();
|
||||
};
|
||||
|
||||
CanvasRenderingContext2D includes CanvasState;
|
||||
CanvasRenderingContext2D includes CanvasTransform;
|
||||
CanvasRenderingContext2D includes CanvasCompositing;
|
||||
CanvasRenderingContext2D includes CanvasImageSmoothing;
|
||||
CanvasRenderingContext2D includes CanvasFillStrokeStyles;
|
||||
CanvasRenderingContext2D includes CanvasShadowStyles;
|
||||
CanvasRenderingContext2D includes CanvasFilters;
|
||||
CanvasRenderingContext2D includes CanvasRect;
|
||||
CanvasRenderingContext2D includes CanvasDrawPath;
|
||||
CanvasRenderingContext2D includes CanvasUserInterface;
|
||||
CanvasRenderingContext2D includes CanvasText;
|
||||
CanvasRenderingContext2D includes CanvasDrawImage;
|
||||
CanvasRenderingContext2D includes CanvasImageData;
|
||||
CanvasRenderingContext2D includes CanvasPathDrawingStyles;
|
||||
CanvasRenderingContext2D includes CanvasTextDrawingStyles;
|
||||
CanvasRenderingContext2D includes CanvasPath;
|
41
Libraries/LibWeb/HTML/CloseEvent.cpp
Normal file
41
Libraries/LibWeb/HTML/CloseEvent.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/CloseEventPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/CloseEvent.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CloseEvent);
|
||||
|
||||
JS::NonnullGCPtr<CloseEvent> CloseEvent::create(JS::Realm& realm, FlyString const& event_name, CloseEventInit const& event_init)
|
||||
{
|
||||
return realm.heap().allocate<CloseEvent>(realm, realm, event_name, event_init);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CloseEvent>> CloseEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, CloseEventInit const& event_init)
|
||||
{
|
||||
return create(realm, event_name, event_init);
|
||||
}
|
||||
|
||||
CloseEvent::CloseEvent(JS::Realm& realm, FlyString const& event_name, CloseEventInit const& event_init)
|
||||
: DOM::Event(realm, event_name, event_init)
|
||||
, m_was_clean(event_init.was_clean)
|
||||
, m_code(event_init.code)
|
||||
, m_reason(event_init.reason)
|
||||
{
|
||||
}
|
||||
|
||||
CloseEvent::~CloseEvent() = default;
|
||||
|
||||
void CloseEvent::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CloseEvent);
|
||||
}
|
||||
|
||||
}
|
45
Libraries/LibWeb/HTML/CloseEvent.h
Normal file
45
Libraries/LibWeb/HTML/CloseEvent.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FlyString.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct CloseEventInit : public DOM::EventInit {
|
||||
bool was_clean { false };
|
||||
u16 code { 0 };
|
||||
String reason;
|
||||
};
|
||||
|
||||
class CloseEvent : public DOM::Event {
|
||||
WEB_PLATFORM_OBJECT(CloseEvent, DOM::Event);
|
||||
JS_DECLARE_ALLOCATOR(CloseEvent);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static JS::NonnullGCPtr<CloseEvent> create(JS::Realm&, FlyString const& event_name, CloseEventInit const& event_init = {});
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<CloseEvent>> construct_impl(JS::Realm&, FlyString const& event_name, CloseEventInit const& event_init);
|
||||
|
||||
virtual ~CloseEvent() override;
|
||||
|
||||
bool was_clean() const { return m_was_clean; }
|
||||
u16 code() const { return m_code; }
|
||||
String reason() const { return m_reason; }
|
||||
|
||||
private:
|
||||
CloseEvent(JS::Realm&, FlyString const& event_name, CloseEventInit const& event_init);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
bool m_was_clean { false };
|
||||
u16 m_code { 0 };
|
||||
String m_reason;
|
||||
};
|
||||
|
||||
}
|
17
Libraries/LibWeb/HTML/CloseEvent.idl
Normal file
17
Libraries/LibWeb/HTML/CloseEvent.idl
Normal file
|
@ -0,0 +1,17 @@
|
|||
#import <DOM/Event.idl>
|
||||
|
||||
// https://websockets.spec.whatwg.org/#the-closeevent-interface
|
||||
[Exposed=*]
|
||||
interface CloseEvent : Event {
|
||||
constructor(DOMString type, optional CloseEventInit eventInitDict = {});
|
||||
|
||||
readonly attribute boolean wasClean;
|
||||
readonly attribute unsigned short code;
|
||||
readonly attribute USVString reason;
|
||||
};
|
||||
|
||||
dictionary CloseEventInit : EventInit {
|
||||
boolean wasClean = false;
|
||||
unsigned short code = 0;
|
||||
USVString reason = "";
|
||||
};
|
173
Libraries/LibWeb/HTML/CloseWatcher.cpp
Normal file
173
Libraries/LibWeb/HTML/CloseWatcher.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/Bindings/CloseWatcherPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/EventDispatcher.h>
|
||||
#include <LibWeb/DOM/IDLEventListener.h>
|
||||
#include <LibWeb/HTML/CloseWatcher.h>
|
||||
#include <LibWeb/HTML/CloseWatcherManager.h>
|
||||
#include <LibWeb/HTML/EventHandler.h>
|
||||
#include <LibWeb/HTML/HTMLIFrameElement.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CloseWatcher);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#establish-a-close-watcher
|
||||
JS::NonnullGCPtr<CloseWatcher> 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.heap().allocate<CloseWatcher>(window.realm(), 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<JS::NonnullGCPtr<CloseWatcher>> CloseWatcher::construct_impl(JS::Realm& realm, CloseWatcherOptions const& options)
|
||||
{
|
||||
auto& window = verify_cast<HTML::Window>(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 (options.signal) {
|
||||
// FIXME: 3.1 If options["signal"]'s aborted, then destroy closeWatcher.
|
||||
// FIXME: 3.2 Add the following steps to options["signal"]:
|
||||
}
|
||||
|
||||
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<HTML::Window>(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<HTML::Window>(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<HTML::Window>(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);
|
||||
}
|
||||
|
||||
}
|
49
Libraries/LibWeb/HTML/CloseWatcher.h
Normal file
49
Libraries/LibWeb/HTML/CloseWatcher.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#closewatcheroptions
|
||||
struct CloseWatcherOptions {
|
||||
JS::GCPtr<DOM::AbortSignal> signal;
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#the-closewatcher-interface
|
||||
class CloseWatcher final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(CloseWatcher, DOM::EventTarget);
|
||||
JS_DECLARE_ALLOCATOR(CloseWatcher);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<CloseWatcher>> construct_impl(JS::Realm&, CloseWatcherOptions const& = {});
|
||||
[[nodiscard]] static JS::NonnullGCPtr<CloseWatcher> establish(HTML::Window&);
|
||||
|
||||
bool request_close();
|
||||
void close();
|
||||
void destroy();
|
||||
|
||||
virtual ~CloseWatcher() override = default;
|
||||
|
||||
void set_oncancel(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* oncancel();
|
||||
|
||||
void set_onclose(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onclose();
|
||||
|
||||
private:
|
||||
CloseWatcher(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
bool m_is_running_cancel_action { false };
|
||||
bool m_is_active { true };
|
||||
};
|
||||
|
||||
}
|
19
Libraries/LibWeb/HTML/CloseWatcher.idl
Normal file
19
Libraries/LibWeb/HTML/CloseWatcher.idl
Normal file
|
@ -0,0 +1,19 @@
|
|||
#import <DOM/EventTarget.idl>
|
||||
#import <DOM/EventHandler.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#closewatcher
|
||||
[Exposed=Window]
|
||||
interface CloseWatcher : EventTarget {
|
||||
constructor(optional CloseWatcherOptions options = {});
|
||||
|
||||
undefined requestClose();
|
||||
undefined close();
|
||||
undefined destroy();
|
||||
|
||||
attribute EventHandler oncancel;
|
||||
attribute EventHandler onclose;
|
||||
};
|
||||
|
||||
dictionary CloseWatcherOptions {
|
||||
AbortSignal signal;
|
||||
};
|
120
Libraries/LibWeb/HTML/CloseWatcherManager.cpp
Normal file
120
Libraries/LibWeb/HTML/CloseWatcherManager.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/EventDispatcher.h>
|
||||
#include <LibWeb/DOM/IDLEventListener.h>
|
||||
#include <LibWeb/HTML/CloseWatcher.h>
|
||||
#include <LibWeb/HTML/CloseWatcherManager.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CloseWatcherManager);
|
||||
|
||||
JS::NonnullGCPtr<CloseWatcherManager> CloseWatcherManager::create(JS::Realm& realm)
|
||||
{
|
||||
return realm.heap().allocate<CloseWatcherManager>(realm, realm);
|
||||
}
|
||||
|
||||
CloseWatcherManager::CloseWatcherManager(JS::Realm& realm)
|
||||
: PlatformObject(realm)
|
||||
{
|
||||
}
|
||||
|
||||
void CloseWatcherManager::add(JS::NonnullGCPtr<CloseWatcher> close_watcher)
|
||||
{
|
||||
// If manager's groups's size is less than manager's allowed number of groups
|
||||
if (m_groups.size() < m_allowed_number_of_groups) {
|
||||
// then append « closeWatcher » to manager's groups.
|
||||
JS::MarkedVector<JS::NonnullGCPtr<CloseWatcher>> new_group(realm().heap());
|
||||
new_group.append(close_watcher);
|
||||
m_groups.append(move(new_group));
|
||||
} else {
|
||||
// Assert: manager's groups's size is at least 1 in this branch, since manager's allowed number of groups is always at least 1.
|
||||
VERIFY(!m_groups.is_empty());
|
||||
// Append closeWatcher to manager's groups's last item.
|
||||
m_groups.last().append(close_watcher);
|
||||
}
|
||||
|
||||
// Set manager's next user interaction allows a new group to true.
|
||||
m_next_user_interaction_allows_a_new_group = true;
|
||||
}
|
||||
|
||||
void CloseWatcherManager::remove(CloseWatcher const& close_watcher)
|
||||
{
|
||||
// 2. For each group of manager's groups: remove closeWatcher from group
|
||||
for (auto& group : m_groups) {
|
||||
group.remove_first_matching([&close_watcher](JS::NonnullGCPtr<CloseWatcher>& entry) {
|
||||
return entry.ptr() == &close_watcher;
|
||||
});
|
||||
}
|
||||
// 3. Remove any item from manager's group that is empty
|
||||
m_groups.remove_all_matching([](auto& group) { return group.is_empty(); });
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#process-close-watchers
|
||||
bool CloseWatcherManager::process_close_watchers()
|
||||
{
|
||||
// 1. Let processedACloseWatcher be false.
|
||||
bool processed_a_close_watcher = false;
|
||||
// 2. If window's close watcher manager's groups is not empty:
|
||||
if (!m_groups.is_empty()) {
|
||||
// 2.1 Let group be the last item in window's close watcher manager's groups.
|
||||
auto& group = m_groups.last();
|
||||
// Ambiguous spec wording. We copy the groups to avoid modifying the original while iterating.
|
||||
// See https://github.com/whatwg/html/issues/10240
|
||||
JS::MarkedVector<JS::NonnullGCPtr<CloseWatcher>> group_copy(realm().heap());
|
||||
group_copy.ensure_capacity(group.size());
|
||||
for (auto& close_watcher : group) {
|
||||
group_copy.append(close_watcher);
|
||||
}
|
||||
// 2.2 For each closeWatcher of group, in reverse order:
|
||||
for (auto it = group_copy.rbegin(); it != group_copy.rend(); ++it) {
|
||||
// 2.1.1 Set processedACloseWatcher to true.
|
||||
processed_a_close_watcher = true;
|
||||
// 2.1.2 Let shouldProceed be the result of requesting to close closeWatcher.
|
||||
bool should_proceed = (*it)->request_close();
|
||||
// 2.1.3 If shouldProceed is false, then break;
|
||||
if (!should_proceed)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 3. If window's close watcher manager's allowed number of groups is greater than 1, decrement it by 1.
|
||||
if (m_allowed_number_of_groups > 1)
|
||||
m_allowed_number_of_groups--;
|
||||
|
||||
// 4. Return processedACloseWatcher.
|
||||
return processed_a_close_watcher;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#notify-the-close-watcher-manager-about-user-activation
|
||||
void CloseWatcherManager::notify_about_user_activation()
|
||||
{
|
||||
// 1. Let manager be window's close watcher manager.
|
||||
// 2. If manager's next user interaction allows a new group is true, then increment manager's allowed number of groups.
|
||||
if (m_next_user_interaction_allows_a_new_group)
|
||||
m_allowed_number_of_groups++;
|
||||
// 3. Set manager's next user interaction allows a new group to false.
|
||||
m_next_user_interaction_allows_a_new_group = false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close
|
||||
bool CloseWatcherManager::can_prevent_close()
|
||||
{
|
||||
// 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...
|
||||
return m_groups.size() < m_allowed_number_of_groups;
|
||||
}
|
||||
|
||||
void CloseWatcherManager::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
|
||||
visitor.visit(m_groups);
|
||||
}
|
||||
|
||||
}
|
40
Libraries/LibWeb/HTML/CloseWatcherManager.h
Normal file
40
Libraries/LibWeb/HTML/CloseWatcherManager.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-manager
|
||||
class CloseWatcherManager final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(CloseWatcherManager, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(CloseWatcherManager);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static JS::NonnullGCPtr<CloseWatcherManager> create(JS::Realm&);
|
||||
|
||||
void add(JS::NonnullGCPtr<CloseWatcher>);
|
||||
void remove(CloseWatcher const&);
|
||||
|
||||
bool process_close_watchers();
|
||||
|
||||
void notify_about_user_activation();
|
||||
bool can_prevent_close();
|
||||
|
||||
private:
|
||||
explicit CloseWatcherManager(JS::Realm&);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
Vector<Vector<JS::NonnullGCPtr<CloseWatcher>>> m_groups;
|
||||
uint32_t m_allowed_number_of_groups { 1 };
|
||||
bool m_next_user_interaction_allows_a_new_group { true };
|
||||
};
|
||||
|
||||
}
|
16
Libraries/LibWeb/HTML/ColorPickerUpdateState.h
Normal file
16
Libraries/LibWeb/HTML/ColorPickerUpdateState.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
enum class ColorPickerUpdateState {
|
||||
Update,
|
||||
Closed,
|
||||
};
|
||||
|
||||
}
|
260
Libraries/LibWeb/HTML/CrossOrigin/AbstractOperations.cpp
Normal file
260
Libraries/LibWeb/HTML/CrossOrigin/AbstractOperations.cpp
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/PropertyDescriptor.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/HTML/CrossOrigin/AbstractOperations.h>
|
||||
#include <LibWeb/HTML/Location.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// 7.2.3.1 CrossOriginProperties ( O ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginproperties-(-o-)
|
||||
Vector<CrossOriginProperty> cross_origin_properties(Variant<HTML::Location const*, HTML::Window const*> const& object)
|
||||
{
|
||||
// 1. Assert: O is a Location or Window object.
|
||||
|
||||
return object.visit(
|
||||
// 2. If O is a Location object, then return « { [[Property]]: "href", [[NeedsGet]]: false, [[NeedsSet]]: true }, { [[Property]]: "replace" } ».
|
||||
[](HTML::Location const*) -> Vector<CrossOriginProperty> {
|
||||
return {
|
||||
{ .property = "href"_string, .needs_get = false, .needs_set = true },
|
||||
{ .property = "replace"_string },
|
||||
};
|
||||
},
|
||||
// 3. Return « { [[Property]]: "window", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "self", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "location", [[NeedsGet]]: true, [[NeedsSet]]: true }, { [[Property]]: "close" }, { [[Property]]: "closed", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "focus" }, { [[Property]]: "blur" }, { [[Property]]: "frames", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "length", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "top", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "opener", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "parent", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "postMessage" } ».
|
||||
[](HTML::Window const*) -> Vector<CrossOriginProperty> {
|
||||
return {
|
||||
{ .property = "window"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "self"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "location"_string, .needs_get = true, .needs_set = true },
|
||||
{ .property = "close"_string },
|
||||
{ .property = "closed"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "focus"_string },
|
||||
{ .property = "blur"_string },
|
||||
{ .property = "frames"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "length"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "top"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "opener"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "parent"_string, .needs_get = true, .needs_set = false },
|
||||
{ .property = "postMessage"_string },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#cross-origin-accessible-window-property-name
|
||||
bool is_cross_origin_accessible_window_property_name(JS::PropertyKey const& property_key)
|
||||
{
|
||||
// A JavaScript property name P is a cross-origin accessible window property name if it is "window", "self", "location", "close", "closed", "focus", "blur", "frames", "length", "top", "opener", "parent", "postMessage", or an array index property name.
|
||||
static Array<DeprecatedFlyString, 13> property_names {
|
||||
"window"sv, "self"sv, "location"sv, "close"sv, "closed"sv, "focus"sv, "blur"sv, "frames"sv, "length"sv, "top"sv, "opener"sv, "parent"sv, "postMessage"sv
|
||||
};
|
||||
return (property_key.is_string() && any_of(property_names, [&](auto const& name) { return property_key.as_string() == name; })) || property_key.is_number();
|
||||
}
|
||||
|
||||
// 7.2.3.2 CrossOriginPropertyFallback ( P ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginpropertyfallback-(-p-)
|
||||
JS::ThrowCompletionOr<JS::PropertyDescriptor> cross_origin_property_fallback(JS::VM& vm, JS::PropertyKey const& property_key)
|
||||
{
|
||||
// 1. If P is "then", @@toStringTag, @@hasInstance, or @@isConcatSpreadable, then return PropertyDescriptor{ [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
|
||||
auto property_key_is_then = property_key.is_string() && property_key.as_string() == vm.names.then.as_string();
|
||||
auto property_key_is_allowed_symbol = property_key.is_symbol()
|
||||
&& (property_key.as_symbol() == vm.well_known_symbol_to_string_tag()
|
||||
|| property_key.as_symbol() == vm.well_known_symbol_has_instance()
|
||||
|| property_key.as_symbol() == vm.well_known_symbol_is_concat_spreadable());
|
||||
if (property_key_is_then || property_key_is_allowed_symbol)
|
||||
return JS::PropertyDescriptor { .value = JS::js_undefined(), .writable = false, .enumerable = false, .configurable = true };
|
||||
|
||||
// 2. Throw a "SecurityError" DOMException.
|
||||
return throw_completion(WebIDL::SecurityError::create(*vm.current_realm(), MUST(String::formatted("Can't access property '{}' on cross-origin object", property_key))));
|
||||
}
|
||||
|
||||
// 7.2.3.3 IsPlatformObjectSameOrigin ( O ), https://html.spec.whatwg.org/multipage/nav-history-apis.html#isplatformobjectsameorigin-(-o-)
|
||||
// https://whatpr.org/html/9893/nav-history-apis.html#isplatformobjectsameorigin-(-o-)
|
||||
bool is_platform_object_same_origin(JS::Object const& object)
|
||||
{
|
||||
// 1. Return true if the current principal settings object's origin is same origin-domain with O's relevant settings object's origin, and false otherwise.
|
||||
return HTML::current_principal_settings_object().origin().is_same_origin_domain(HTML::relevant_settings_object(object).origin());
|
||||
}
|
||||
|
||||
// 7.2.3.4 CrossOriginGetOwnPropertyHelper ( O, P ), https://html.spec.whatwg.org/multipage/nav-history-apis.html#crossorigingetownpropertyhelper-(-o,-p-)
|
||||
// https://whatpr.org/html/9893/nav-history-apis.html#crossorigingetownpropertyhelper-(-o,-p-)
|
||||
Optional<JS::PropertyDescriptor> cross_origin_get_own_property_helper(Variant<HTML::Location*, HTML::Window*> const& object, JS::PropertyKey const& property_key)
|
||||
{
|
||||
auto& realm = *Bindings::main_thread_vm().current_realm();
|
||||
auto const* object_ptr = object.visit([](auto* o) { return static_cast<JS::Object const*>(o); });
|
||||
auto const object_const_variant = object.visit([](auto* o) { return Variant<HTML::Location const*, HTML::Window const*> { o }; });
|
||||
|
||||
// 1. Let crossOriginKey be a tuple consisting of the current principal settings object, O's relevant settings object, and P.
|
||||
auto cross_origin_key = CrossOriginKey {
|
||||
.current_principal_settings_object = (FlatPtr)&HTML::current_principal_settings_object(),
|
||||
.relevant_settings_object = (FlatPtr)&HTML::relevant_settings_object(*object_ptr),
|
||||
.property_key = property_key,
|
||||
};
|
||||
|
||||
// SameValue(e.[[Property]], P) can never be true at step 2.1 if P is not a string due to the different type, so we can return early.
|
||||
if (!property_key.is_string()) {
|
||||
return {};
|
||||
}
|
||||
auto const& property_key_string = MUST(FlyString::from_deprecated_fly_string(property_key.as_string()));
|
||||
|
||||
// 2. For each e of CrossOriginProperties(O):
|
||||
for (auto const& entry : cross_origin_properties(object_const_variant)) {
|
||||
if (entry.property != property_key_string)
|
||||
continue;
|
||||
// 1. If SameValue(e.[[Property]], P) is true, then:
|
||||
auto& cross_origin_property_descriptor_map = object.visit([](auto* o) -> CrossOriginPropertyDescriptorMap& { return o->cross_origin_property_descriptor_map(); });
|
||||
|
||||
// 1. If the value of the [[CrossOriginPropertyDescriptorMap]] internal slot of O contains an entry whose key is crossOriginKey, then return that entry's value.
|
||||
auto it = cross_origin_property_descriptor_map.find(cross_origin_key);
|
||||
if (it != cross_origin_property_descriptor_map.end())
|
||||
return it->value;
|
||||
|
||||
// 2. Let originalDesc be OrdinaryGetOwnProperty(O, P).
|
||||
auto original_descriptor = MUST((object_ptr->JS::Object::internal_get_own_property)(property_key));
|
||||
|
||||
// 3. Let crossOriginDesc be undefined.
|
||||
auto cross_origin_descriptor = JS::PropertyDescriptor {};
|
||||
|
||||
// 4. If e.[[NeedsGet]] and e.[[NeedsSet]] are absent, then:
|
||||
if (!entry.needs_get.has_value() && !entry.needs_set.has_value()) {
|
||||
// 1. Let value be originalDesc.[[Value]].
|
||||
auto value = original_descriptor->value;
|
||||
|
||||
// 2. If IsCallable(value) is true, then set value to an anonymous built-in function, created in the current Realm Record, that performs the same steps as the IDL operation P on object O.
|
||||
if (value->is_function()) {
|
||||
value = JS::NativeFunction::create(
|
||||
realm, [function = JS::make_handle(*value)](auto& vm) {
|
||||
return JS::call(vm, function.value(), JS::js_undefined(), vm.running_execution_context().arguments.span());
|
||||
},
|
||||
0, "");
|
||||
}
|
||||
|
||||
// 3. Set crossOriginDesc to PropertyDescriptor{ [[Value]]: value, [[Enumerable]]: false, [[Writable]]: false, [[Configurable]]: true }.
|
||||
cross_origin_descriptor = JS::PropertyDescriptor { .value = value, .writable = false, .enumerable = false, .configurable = true };
|
||||
}
|
||||
// 5. Otherwise:
|
||||
else {
|
||||
// 1. Let crossOriginGet be undefined.
|
||||
Optional<JS::GCPtr<JS::FunctionObject>> cross_origin_get;
|
||||
|
||||
// 2. If e.[[NeedsGet]] is true, then set crossOriginGet to an anonymous built-in function, created in the current Realm Record, that performs the same steps as the getter of the IDL attribute P on object O.
|
||||
if (*entry.needs_get) {
|
||||
cross_origin_get = JS::NativeFunction::create(
|
||||
realm, [object_ptr, getter = JS::make_handle(*original_descriptor->get)](auto& vm) {
|
||||
return JS::call(vm, getter.cell(), object_ptr, vm.running_execution_context().arguments.span());
|
||||
},
|
||||
0, "");
|
||||
}
|
||||
|
||||
// 3. Let crossOriginSet be undefined.
|
||||
Optional<JS::GCPtr<JS::FunctionObject>> cross_origin_set;
|
||||
|
||||
// If e.[[NeedsSet]] is true, then set crossOriginSet to an anonymous built-in function, created in the current Realm Record, that performs the same steps as the setter of the IDL attribute P on object O.
|
||||
if (*entry.needs_set) {
|
||||
cross_origin_set = JS::NativeFunction::create(
|
||||
realm, [object_ptr, setter = JS::make_handle(*original_descriptor->set)](auto& vm) {
|
||||
return JS::call(vm, setter.cell(), object_ptr, vm.running_execution_context().arguments.span());
|
||||
},
|
||||
0, "");
|
||||
}
|
||||
|
||||
// 5. Set crossOriginDesc to PropertyDescriptor{ [[Get]]: crossOriginGet, [[Set]]: crossOriginSet, [[Enumerable]]: false, [[Configurable]]: true }.
|
||||
cross_origin_descriptor = JS::PropertyDescriptor { .get = cross_origin_get, .set = cross_origin_set, .enumerable = false, .configurable = true };
|
||||
}
|
||||
|
||||
// 6. Create an entry in the value of the [[CrossOriginPropertyDescriptorMap]] internal slot of O with key crossOriginKey and value crossOriginDesc.
|
||||
cross_origin_property_descriptor_map.set(cross_origin_key, cross_origin_descriptor);
|
||||
|
||||
// 7. Return crossOriginDesc.
|
||||
return cross_origin_descriptor;
|
||||
}
|
||||
|
||||
// 3. Return undefined.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 7.2.3.5 CrossOriginGet ( O, P, Receiver ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginget-(-o,-p,-receiver-)
|
||||
JS::ThrowCompletionOr<JS::Value> cross_origin_get(JS::VM& vm, JS::Object const& object, JS::PropertyKey const& property_key, JS::Value receiver)
|
||||
{
|
||||
// 1. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
auto descriptor = TRY(object.internal_get_own_property(property_key));
|
||||
|
||||
// 2. Assert: desc is not undefined.
|
||||
VERIFY(descriptor.has_value());
|
||||
|
||||
// 3. If IsDataDescriptor(desc) is true, then return desc.[[Value]].
|
||||
if (descriptor->is_data_descriptor())
|
||||
return *descriptor->value;
|
||||
|
||||
// 4. Assert: IsAccessorDescriptor(desc) is true.
|
||||
VERIFY(descriptor->is_accessor_descriptor());
|
||||
|
||||
// 5. Let getter be desc.[[Get]].
|
||||
auto& getter = descriptor->get;
|
||||
|
||||
// 6. If getter is undefined, then throw a "SecurityError" DOMException.
|
||||
if (!getter.has_value())
|
||||
return throw_completion(WebIDL::SecurityError::create(*vm.current_realm(), MUST(String::formatted("Can't get property '{}' on cross-origin object", property_key))));
|
||||
|
||||
// 7. Return ? Call(getter, Receiver).
|
||||
return JS::call(vm, *getter, receiver);
|
||||
}
|
||||
|
||||
// 7.2.3.6 CrossOriginSet ( O, P, V, Receiver ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginset-(-o,-p,-v,-receiver-)
|
||||
JS::ThrowCompletionOr<bool> cross_origin_set(JS::VM& vm, JS::Object& object, JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver)
|
||||
{
|
||||
// 1. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
auto descriptor = TRY(object.internal_get_own_property(property_key));
|
||||
|
||||
// 2. Assert: desc is not undefined.
|
||||
VERIFY(descriptor.has_value());
|
||||
|
||||
// 3. If desc.[[Set]] is present and its value is not undefined, then:
|
||||
if (descriptor->set.has_value() && *descriptor->set) {
|
||||
// FIXME: Spec issue, `setter` isn't being defined.
|
||||
// 1. Perform ? Call(setter, Receiver, «V»).
|
||||
TRY(JS::call(vm, *descriptor->set, receiver, value));
|
||||
|
||||
// 2. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. Throw a "SecurityError" DOMException.
|
||||
return throw_completion(WebIDL::SecurityError::create(*vm.current_realm(), MUST(String::formatted("Can't set property '{}' on cross-origin object", property_key))));
|
||||
}
|
||||
|
||||
// 7.2.3.7 CrossOriginOwnPropertyKeys ( O ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginownpropertykeys-(-o-)
|
||||
JS::MarkedVector<JS::Value> cross_origin_own_property_keys(Variant<HTML::Location const*, HTML::Window const*> const& object)
|
||||
{
|
||||
auto& event_loop = HTML::main_thread_event_loop();
|
||||
auto& vm = event_loop.vm();
|
||||
|
||||
// 1. Let keys be a new empty List.
|
||||
auto keys = JS::MarkedVector<JS::Value> { vm.heap() };
|
||||
|
||||
// 2. For each e of CrossOriginProperties(O), append e.[[Property]] to keys.
|
||||
for (auto& entry : cross_origin_properties(object))
|
||||
keys.append(JS::PrimitiveString::create(vm, move(entry.property)));
|
||||
|
||||
// 3. Return the concatenation of keys and « "then", @@toStringTag, @@hasInstance, @@isConcatSpreadable ».
|
||||
keys.append(JS::PrimitiveString::create(vm, vm.names.then.as_string()));
|
||||
keys.append(vm.well_known_symbol_to_string_tag());
|
||||
keys.append(vm.well_known_symbol_has_instance());
|
||||
keys.append(vm.well_known_symbol_is_concat_spreadable());
|
||||
return keys;
|
||||
}
|
||||
|
||||
}
|
26
Libraries/LibWeb/HTML/CrossOrigin/AbstractOperations.h
Normal file
26
Libraries/LibWeb/HTML/CrossOrigin/AbstractOperations.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/CrossOrigin/CrossOriginPropertyDescriptorMap.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
Vector<CrossOriginProperty> cross_origin_properties(Variant<HTML::Location const*, HTML::Window const*> const&);
|
||||
bool is_cross_origin_accessible_window_property_name(JS::PropertyKey const&);
|
||||
JS::ThrowCompletionOr<JS::PropertyDescriptor> cross_origin_property_fallback(JS::VM&, JS::PropertyKey const&);
|
||||
bool is_platform_object_same_origin(JS::Object const&);
|
||||
Optional<JS::PropertyDescriptor> cross_origin_get_own_property_helper(Variant<HTML::Location*, HTML::Window*> const&, JS::PropertyKey const&);
|
||||
JS::ThrowCompletionOr<JS::Value> cross_origin_get(JS::VM&, JS::Object const&, JS::PropertyKey const&, JS::Value receiver);
|
||||
JS::ThrowCompletionOr<bool> cross_origin_set(JS::VM&, JS::Object&, JS::PropertyKey const&, JS::Value, JS::Value receiver);
|
||||
JS::MarkedVector<JS::Value> cross_origin_own_property_keys(Variant<HTML::Location const*, HTML::Window const*> const&);
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Traits.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct CrossOriginProperty {
|
||||
String property;
|
||||
Optional<bool> needs_get {};
|
||||
Optional<bool> needs_set {};
|
||||
};
|
||||
|
||||
struct CrossOriginKey {
|
||||
FlatPtr current_principal_settings_object;
|
||||
FlatPtr relevant_settings_object;
|
||||
JS::PropertyKey property_key;
|
||||
};
|
||||
|
||||
using CrossOriginPropertyDescriptorMap = HashMap<CrossOriginKey, JS::PropertyDescriptor>;
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<>
|
||||
struct Traits<Web::HTML::CrossOriginKey> : public DefaultTraits<Web::HTML::CrossOriginKey> {
|
||||
static unsigned hash(Web::HTML::CrossOriginKey const& key)
|
||||
{
|
||||
return pair_int_hash(
|
||||
Traits<JS::PropertyKey>::hash(key.property_key),
|
||||
pair_int_hash(ptr_hash(key.current_principal_settings_object), ptr_hash(key.relevant_settings_object)));
|
||||
}
|
||||
|
||||
static bool equals(Web::HTML::CrossOriginKey const& a, Web::HTML::CrossOriginKey const& b)
|
||||
{
|
||||
return a.current_principal_settings_object == b.current_principal_settings_object
|
||||
&& a.relevant_settings_object == b.relevant_settings_object
|
||||
&& Traits<JS::PropertyKey>::equals(a.property_key, b.property_key);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
36
Libraries/LibWeb/HTML/CrossOrigin/OpenerPolicy.h
Normal file
36
Libraries/LibWeb/HTML/CrossOrigin/OpenerPolicy.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#cross-origin-opener-policy-value
|
||||
enum class OpenerPolicyValue {
|
||||
UnsafeNone,
|
||||
SameOriginAllowPopups,
|
||||
SameOrigin,
|
||||
SameOriginPlusCOEP,
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#cross-origin-opener-policy
|
||||
struct OpenerPolicy {
|
||||
// A value, which is an opener policy value, initially "unsafe-none".
|
||||
OpenerPolicyValue value { OpenerPolicyValue::UnsafeNone };
|
||||
|
||||
// A reporting endpoint, which is string or null, initially null.
|
||||
Optional<String> reporting_endpoint;
|
||||
|
||||
// A report-only value, which is an opener policy value, initially "unsafe-none".
|
||||
OpenerPolicyValue report_only_value { OpenerPolicyValue::UnsafeNone };
|
||||
|
||||
// A report-only reporting endpoint, which is a string or null, initially null.
|
||||
Optional<String> report_only_reporting_endpoint;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/HTML/CrossOrigin/OpenerPolicy.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#coop-enforcement-result
|
||||
struct OpenerPolicyEnforcementResult {
|
||||
// A boolean needs a browsing context group switch, initially false.
|
||||
bool needs_a_browsing_context_group_switch { false };
|
||||
|
||||
// A boolean would need a browsing context group switch due to report-only, initially false.
|
||||
bool would_need_a_browsing_context_group_switch_due_to_report_only { false };
|
||||
|
||||
// A URL url.
|
||||
URL::URL url;
|
||||
|
||||
// An origin origin.
|
||||
URL::Origin origin;
|
||||
|
||||
// An opener policy.
|
||||
OpenerPolicy opener_policy;
|
||||
|
||||
// A boolean current context is navigation source.
|
||||
bool current_context_is_navigation_source { false };
|
||||
};
|
||||
|
||||
}
|
92
Libraries/LibWeb/HTML/CrossOrigin/Reporting.cpp
Normal file
92
Libraries/LibWeb/HTML/CrossOrigin/Reporting.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/CrossOrigin/AbstractOperations.h>
|
||||
#include <LibWeb/HTML/CrossOrigin/Reporting.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#coop-check-access-report
|
||||
void check_if_access_between_two_browsing_contexts_should_be_reported(
|
||||
BrowsingContext const& accessor,
|
||||
BrowsingContext const* accessed,
|
||||
JS::PropertyKey const& property_key,
|
||||
EnvironmentSettingsObject const& environment)
|
||||
{
|
||||
// FIXME: Spec bug: https://github.com/whatwg/html/issues/10192
|
||||
if (!accessed)
|
||||
return;
|
||||
|
||||
// 1. If propertyKey is not a cross-origin accessible window property name, then return.
|
||||
if (!is_cross_origin_accessible_window_property_name(property_key))
|
||||
return;
|
||||
|
||||
// 2. Assert: accessor's active document and accessed's active document are both fully active.
|
||||
VERIFY(accessor.active_document()->is_fully_active());
|
||||
VERIFY(accessed->active_document()->is_fully_active());
|
||||
|
||||
// 3. Let accessorTopDocument be accessor's top-level browsing context's active document.
|
||||
auto* accessor_top_document = accessor.top_level_browsing_context()->active_document();
|
||||
|
||||
// 4. Let accessorInclusiveAncestorOrigins be the list obtained by taking the origin of the active document of each of accessor's active document's inclusive ancestor navigables.
|
||||
Vector<URL::Origin> accessor_inclusive_ancestor_origins = {};
|
||||
auto accessor_inclusive_ancestors = accessor.active_document()->ancestor_navigables();
|
||||
accessor_inclusive_ancestor_origins.ensure_capacity(accessor_inclusive_ancestors.size());
|
||||
for (auto const& ancestor : accessor_inclusive_ancestors) {
|
||||
VERIFY(ancestor != nullptr);
|
||||
VERIFY(ancestor->active_document() != nullptr);
|
||||
accessor_inclusive_ancestor_origins.append(ancestor->active_document()->origin());
|
||||
}
|
||||
|
||||
// 5. Let accessedTopDocument be accessed's top-level browsing context's active document.
|
||||
VERIFY(accessed->top_level_browsing_context() != nullptr);
|
||||
auto* accessed_top_document = accessed->top_level_browsing_context()->active_document();
|
||||
|
||||
// 6. Let accessedInclusiveAncestorOrigins be the list obtained by taking the origin of the active document of each of accessed's active document's inclusive ancestor navigables.
|
||||
Vector<URL::Origin> accessed_inclusive_ancestor_origins = {};
|
||||
auto accessed_inclusive_ancestors = accessed->active_document()->ancestor_navigables();
|
||||
accessed_inclusive_ancestor_origins.ensure_capacity(accessed_inclusive_ancestors.size());
|
||||
for (auto const& ancestor : accessed_inclusive_ancestors) {
|
||||
VERIFY(ancestor != nullptr);
|
||||
VERIFY(ancestor->active_document() != nullptr);
|
||||
accessed_inclusive_ancestor_origins.append(ancestor->active_document()->origin());
|
||||
}
|
||||
|
||||
// 7. If any of accessorInclusiveAncestorOrigins are not same origin with accessorTopDocument's origin, or if any of accessedInclusiveAncestorOrigins are not same origin with accessedTopDocument's origin, then return.
|
||||
for (auto const& origin : accessor_inclusive_ancestor_origins)
|
||||
if (!origin.is_same_origin(accessor_top_document->origin()))
|
||||
return;
|
||||
for (auto const& origin : accessed_inclusive_ancestor_origins)
|
||||
if (!origin.is_same_origin(accessed_top_document->origin()))
|
||||
return;
|
||||
|
||||
// 8. If accessor's top-level browsing context's virtual browsing context group ID is accessed's top-level browsing context's virtual browsing context group ID, then return.
|
||||
if (accessor.top_level_browsing_context()->virtual_browsing_context_group_id() == accessed->top_level_browsing_context()->virtual_browsing_context_group_id())
|
||||
return;
|
||||
|
||||
// 9. Let accessorAccessedRelationship be a new accessor-accessed relationship with value none.
|
||||
auto accessor_accessed_relationship = AccessorAccessedRelationship::None;
|
||||
|
||||
// 10. If accessed's top-level browsing context's opener browsing context is accessor or is an ancestor of accessor, then set accessorAccessedRelationship to accessor is opener.
|
||||
if (accessor.is_ancestor_of(*accessed->top_level_browsing_context()->opener_browsing_context()))
|
||||
accessor_accessed_relationship = AccessorAccessedRelationship::AccessorIsOpener;
|
||||
|
||||
// 11. If accessor's top-level browsing context's opener browsing context is accessed or is an ancestor of accessed, then set accessorAccessedRelationship to accessor is openee.
|
||||
if (accessed->is_ancestor_of(*accessor.top_level_browsing_context()->opener_browsing_context()))
|
||||
accessor_accessed_relationship = AccessorAccessedRelationship::AccessorIsOpener;
|
||||
|
||||
// 12. Queue violation reports for accesses, given accessorAccessedRelationship, accessorTopDocument's opener policy, accessedTopDocument's opener policy, accessor's active document's URL, accessed's active document's URL, accessor's top-level browsing context's initial URL, accessed's top-level browsing context's initial URL, accessor's active document's origin, accessed's active document's origin, accessor's top-level browsing context's opener origin at creation, accessed's top-level browsing context's opener origin at creation, accessorTopDocument's referrer, accessedTopDocument's referrer, propertyKey, and environment.
|
||||
(void)environment;
|
||||
(void)accessor_accessed_relationship;
|
||||
}
|
||||
|
||||
}
|
23
Libraries/LibWeb/HTML/CrossOrigin/Reporting.h
Normal file
23
Libraries/LibWeb/HTML/CrossOrigin/Reporting.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/origin.html#accessor-accessed-relationship
|
||||
enum class AccessorAccessedRelationship {
|
||||
AccessorIsOpener,
|
||||
AccessorIsOpenee,
|
||||
None,
|
||||
};
|
||||
|
||||
void check_if_access_between_two_browsing_contexts_should_be_reported(BrowsingContext const& accessor, BrowsingContext const* accessed, JS::PropertyKey const&, EnvironmentSettingsObject const&);
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
void CustomElementDefinition::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_constructor);
|
||||
visitor.visit(m_lifecycle_callbacks);
|
||||
}
|
||||
|
||||
}
|
114
Libraries/LibWeb/HTML/CustomElements/CustomElementDefinition.h
Normal file
114
Libraries/LibWeb/HTML/CustomElements/CustomElementDefinition.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
#include <LibWeb/WebIDL/CallbackType.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct AlreadyConstructedCustomElementMarker {
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-definition
|
||||
class CustomElementDefinition : public JS::Cell {
|
||||
JS_CELL(CustomElementDefinition, JS::Cell);
|
||||
JS_DECLARE_ALLOCATOR(CustomElementDefinition);
|
||||
|
||||
using LifecycleCallbacksStorage = OrderedHashMap<FlyString, JS::GCPtr<WebIDL::CallbackType>>;
|
||||
using ConstructionStackStorage = Vector<Variant<JS::Handle<DOM::Element>, AlreadyConstructedCustomElementMarker>>;
|
||||
|
||||
static JS::NonnullGCPtr<CustomElementDefinition> create(JS::Realm& realm, String const& name, String const& local_name, WebIDL::CallbackType& constructor, Vector<String>&& observed_attributes, LifecycleCallbacksStorage&& lifecycle_callbacks, bool form_associated, bool disable_internals, bool disable_shadow)
|
||||
{
|
||||
return realm.heap().allocate<CustomElementDefinition>(realm, name, local_name, constructor, move(observed_attributes), move(lifecycle_callbacks), form_associated, disable_internals, disable_shadow);
|
||||
}
|
||||
|
||||
~CustomElementDefinition() = default;
|
||||
|
||||
String const& name() const { return m_name; }
|
||||
String const& local_name() const { return m_local_name; }
|
||||
|
||||
WebIDL::CallbackType& constructor() { return *m_constructor; }
|
||||
WebIDL::CallbackType const& constructor() const { return *m_constructor; }
|
||||
|
||||
Vector<String> const& observed_attributes() const { return m_observed_attributes; }
|
||||
|
||||
LifecycleCallbacksStorage const& lifecycle_callbacks() const { return m_lifecycle_callbacks; }
|
||||
|
||||
ConstructionStackStorage& construction_stack() { return m_construction_stack; }
|
||||
ConstructionStackStorage const& construction_stack() const { return m_construction_stack; }
|
||||
|
||||
bool form_associated() const { return m_form_associated; }
|
||||
bool disable_internals() const { return m_disable_internals; }
|
||||
bool disable_shadow() const { return m_disable_shadow; }
|
||||
|
||||
private:
|
||||
CustomElementDefinition(String const& name, String const& local_name, WebIDL::CallbackType& constructor, Vector<String>&& observed_attributes, LifecycleCallbacksStorage&& lifecycle_callbacks, bool form_associated, bool disable_internals, bool disable_shadow)
|
||||
: m_name(name)
|
||||
, m_local_name(local_name)
|
||||
, m_constructor(constructor)
|
||||
, m_observed_attributes(move(observed_attributes))
|
||||
, m_lifecycle_callbacks(move(lifecycle_callbacks))
|
||||
, m_form_associated(form_associated)
|
||||
, m_disable_internals(disable_internals)
|
||||
, m_disable_shadow(disable_shadow)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void visit_edges(Visitor& visitor) override;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-name
|
||||
// A name
|
||||
// A valid custom element name
|
||||
String m_name;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-local-name
|
||||
// A local name
|
||||
// A local name
|
||||
String m_local_name;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-constructor
|
||||
// A Web IDL CustomElementConstructor callback function type value wrapping the custom element constructor
|
||||
JS::NonnullGCPtr<WebIDL::CallbackType> m_constructor;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-observed-attributes
|
||||
// A list of observed attributes
|
||||
// A sequence<DOMString>
|
||||
Vector<String> m_observed_attributes;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-lifecycle-callbacks
|
||||
// A collection of lifecycle callbacks
|
||||
// A map, whose keys are the strings "connectedCallback", "disconnectedCallback", "adoptedCallback", "attributeChangedCallback",
|
||||
// "formAssociatedCallback", "formDisabledCallback", "formResetCallback", and "formStateRestoreCallback".
|
||||
// The corresponding values are either a Web IDL Function callback function type value, or null.
|
||||
// By default the value of each entry is null.
|
||||
LifecycleCallbacksStorage m_lifecycle_callbacks;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-construction-stack
|
||||
// A construction stack
|
||||
// A list, initially empty, that is manipulated by the upgrade an element algorithm and the HTML element constructors.
|
||||
// Each entry in the list will be either an element or an already constructed marker.
|
||||
ConstructionStackStorage m_construction_stack;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-form-associated
|
||||
// A form-associated boolean
|
||||
// If this is true, user agent treats elements associated to this custom element definition as form-associated custom elements.
|
||||
bool m_form_associated { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-disable-internals
|
||||
// A disable internals boolean
|
||||
// Controls attachInternals().
|
||||
bool m_disable_internals { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-disable-shadow
|
||||
// A disable shadow boolean
|
||||
// Controls attachShadow().
|
||||
bool m_disable_shadow { false };
|
||||
};
|
||||
|
||||
}
|
76
Libraries/LibWeb/HTML/CustomElements/CustomElementName.cpp
Normal file
76
Libraries/LibWeb/HTML/CustomElements/CustomElementName.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts:prod-pcenchar
|
||||
static bool is_pcen_char(u32 code_point)
|
||||
{
|
||||
return code_point == '-'
|
||||
|| code_point == '.'
|
||||
|| (code_point >= '0' && code_point <= '9')
|
||||
|| code_point == '_'
|
||||
|| (code_point >= 'a' && code_point <= 'z')
|
||||
|| code_point == 0xb7
|
||||
|| (code_point >= 0xc0 && code_point <= 0xd6)
|
||||
|| (code_point >= 0xd8 && code_point <= 0xf6)
|
||||
|| (code_point >= 0xf8 && code_point <= 0x37d)
|
||||
|| (code_point >= 0x37f && code_point <= 0x1fff)
|
||||
|| (code_point >= 0x200c && code_point <= 0x200d)
|
||||
|| (code_point >= 0x203f && code_point <= 0x2040)
|
||||
|| (code_point >= 0x2070 && code_point <= 0x218f)
|
||||
|| (code_point >= 0x2c00 && code_point <= 0x2fef)
|
||||
|| (code_point >= 0x3001 && code_point <= 0xD7ff)
|
||||
|| (code_point >= 0xf900 && code_point <= 0xfdcf)
|
||||
|| (code_point >= 0xfdf0 && code_point <= 0xfffd)
|
||||
|| (code_point >= 0x10000 && code_point <= 0xeffff);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
|
||||
bool is_valid_custom_element_name(StringView name)
|
||||
{
|
||||
// name must not be any of the following:
|
||||
if (name.is_one_of(
|
||||
"annotation-xml"sv,
|
||||
"color-profile"sv,
|
||||
"font-face"sv,
|
||||
"font-face-src"sv,
|
||||
"font-face-uri"sv,
|
||||
"font-face-format"sv,
|
||||
"font-face-name"sv,
|
||||
"missing-glyph"sv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// name must match the PotentialCustomElementName production:
|
||||
// PotentialCustomElementName ::=
|
||||
// [a-z] (PCENChar)* '-' (PCENChar)*
|
||||
|
||||
auto code_points = Utf8View { name };
|
||||
auto it = code_points.begin();
|
||||
|
||||
if (code_points.is_empty() || *it < 'a' || *it > 'z')
|
||||
return false;
|
||||
|
||||
++it;
|
||||
|
||||
bool found_hyphen = false;
|
||||
|
||||
for (; it != code_points.end(); ++it) {
|
||||
if (!is_pcen_char(*it))
|
||||
return false;
|
||||
if (*it == '-')
|
||||
found_hyphen = true;
|
||||
}
|
||||
|
||||
return found_hyphen;
|
||||
}
|
||||
|
||||
}
|
15
Libraries/LibWeb/HTML/CustomElements/CustomElementName.h
Normal file
15
Libraries/LibWeb/HTML/CustomElements/CustomElementName.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Srikavin Ramkumar <me@srikavin.me>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
bool is_valid_custom_element_name(StringView name);
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
|
||||
|
||||
namespace Web::HTML::CustomElementReactionNames {
|
||||
|
||||
#define __ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(name) FlyString name;
|
||||
ENUMERATE_CUSTOM_ELEMENT_REACTION_NAMES
|
||||
#undef __ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME
|
||||
|
||||
void initialize_strings()
|
||||
{
|
||||
static bool s_initialized = false;
|
||||
VERIFY(!s_initialized);
|
||||
|
||||
#define __ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(name) \
|
||||
name = #name##_fly_string;
|
||||
ENUMERATE_CUSTOM_ELEMENT_REACTION_NAMES
|
||||
#undef __ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FlyString.h>
|
||||
|
||||
namespace Web::HTML::CustomElementReactionNames {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-lifecycle-callbacks
|
||||
#define ENUMERATE_CUSTOM_ELEMENT_REACTION_NAMES \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(connectedCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(disconnectedCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(adoptedCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(attributeChangedCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(formAssociatedCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(formDisabledCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(formResetCallback) \
|
||||
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(formStateRestoreCallback)
|
||||
|
||||
#define __ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(name) extern FlyString name;
|
||||
ENUMERATE_CUSTOM_ELEMENT_REACTION_NAMES
|
||||
#undef __ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME
|
||||
|
||||
void initialize_strings();
|
||||
|
||||
}
|
431
Libraries/LibWeb/HTML/CustomElements/CustomElementRegistry.cpp
Normal file
431
Libraries/LibWeb/HTML/CustomElements/CustomElementRegistry.cpp
Normal file
|
@ -0,0 +1,431 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <LibWeb/Bindings/CustomElementRegistryPrototype.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(CustomElementRegistry);
|
||||
JS_DEFINE_ALLOCATOR(CustomElementDefinition);
|
||||
|
||||
CustomElementRegistry::CustomElementRegistry(JS::Realm& realm)
|
||||
: Bindings::PlatformObject(realm)
|
||||
{
|
||||
}
|
||||
|
||||
CustomElementRegistry::~CustomElementRegistry() = default;
|
||||
|
||||
void CustomElementRegistry::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CustomElementRegistry);
|
||||
}
|
||||
|
||||
void CustomElementRegistry::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_custom_element_definitions);
|
||||
visitor.visit(m_when_defined_promise_map);
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-callback-function
|
||||
static JS::ThrowCompletionOr<JS::NonnullGCPtr<WebIDL::CallbackType>> convert_value_to_callback_function(JS::VM& vm, JS::Value value)
|
||||
{
|
||||
// FIXME: De-duplicate this from the IDL generator.
|
||||
// 1. If the result of calling IsCallable(V) is false and the conversion to an IDL value is not being performed due to V being assigned to an attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull], then throw a TypeError.
|
||||
if (!value.is_function())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, value.to_string_without_side_effects());
|
||||
|
||||
// 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent settings object as the callback context.
|
||||
return vm.heap().allocate_without_realm<WebIDL::CallbackType>(value.as_object(), HTML::incumbent_settings_object());
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-sequence
|
||||
static JS::ThrowCompletionOr<Vector<String>> convert_value_to_sequence_of_strings(JS::VM& vm, JS::Value value)
|
||||
{
|
||||
// FIXME: De-duplicate this from the IDL generator.
|
||||
// An ECMAScript value V is converted to an IDL sequence<T> value as follows:
|
||||
// 1. If V is not an Object, throw a TypeError.
|
||||
if (!value.is_object())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, value.to_string_without_side_effects());
|
||||
|
||||
// 2. Let method be ? GetMethod(V, @@iterator).
|
||||
auto method = TRY(value.get_method(vm, vm.well_known_symbol_iterator()));
|
||||
|
||||
// 3. If method is undefined, throw a TypeError.
|
||||
if (!method)
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, value.to_string_without_side_effects());
|
||||
|
||||
// 4. Return the result of creating a sequence from V and method.
|
||||
|
||||
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
|
||||
// To create an IDL value of type sequence<T> given an iterable iterable and an iterator getter method, perform the following steps:
|
||||
// 1. Let iter be ? GetIterator(iterable, sync, method).
|
||||
// FIXME: The WebIDL spec is out of date - it should be using GetIteratorFromMethod.
|
||||
auto iterator = TRY(JS::get_iterator_from_method(vm, value, *method));
|
||||
|
||||
// 2. Initialize i to be 0.
|
||||
Vector<String> sequence_of_strings;
|
||||
|
||||
// 3. Repeat
|
||||
for (;;) {
|
||||
// 1. Let next be ? IteratorStep(iter).
|
||||
auto next = TRY(JS::iterator_step(vm, iterator));
|
||||
|
||||
// 2. If next is false, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj.
|
||||
if (!next)
|
||||
return sequence_of_strings;
|
||||
|
||||
// 3. Let nextItem be ? IteratorValue(next).
|
||||
auto next_item = TRY(JS::iterator_value(vm, *next));
|
||||
|
||||
// 4. Initialize Si to the result of converting nextItem to an IDL value of type T.
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-DOMString
|
||||
// An ECMAScript value V is converted to an IDL DOMString value by running the following algorithm:
|
||||
// 1. If V is null and the conversion is to an IDL type associated with the [LegacyNullToEmptyString] extended attribute, then return the DOMString value that represents the empty string.
|
||||
// NOTE: This doesn't apply.
|
||||
|
||||
// 2. Let x be ? ToString(V).
|
||||
// 3. Return the IDL DOMString value that represents the same sequence of code units as the one the ECMAScript String value x represents.
|
||||
auto string_value = TRY(next_item.to_string(vm));
|
||||
|
||||
sequence_of_strings.append(move(string_value));
|
||||
|
||||
// 5. Set i to i + 1.
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define
|
||||
JS::ThrowCompletionOr<void> CustomElementRegistry::define(String const& name, WebIDL::CallbackType* constructor, ElementDefinitionOptions options)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If IsConstructor(constructor) is false, then throw a TypeError.
|
||||
if (!JS::Value(constructor->callback).is_constructor())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAConstructor, JS::Value(constructor->callback).to_string_without_side_effects());
|
||||
|
||||
// 2. If name is not a valid custom element name, then throw a "SyntaxError" DOMException.
|
||||
if (!is_valid_custom_element_name(name))
|
||||
return JS::throw_completion(WebIDL::SyntaxError::create(realm, MUST(String::formatted("'{}' is not a valid custom element name"sv, name))));
|
||||
|
||||
// 3. If this CustomElementRegistry contains an entry with name name, then throw a "NotSupportedError" DOMException.
|
||||
auto existing_definition_with_name_iterator = m_custom_element_definitions.find_if([&name](auto const& definition) {
|
||||
return definition->name() == name;
|
||||
});
|
||||
|
||||
if (existing_definition_with_name_iterator != m_custom_element_definitions.end())
|
||||
return JS::throw_completion(WebIDL::NotSupportedError::create(realm, MUST(String::formatted("A custom element with name '{}' is already defined"sv, name))));
|
||||
|
||||
// 4. If this CustomElementRegistry contains an entry with constructor constructor, then throw a "NotSupportedError" DOMException.
|
||||
auto existing_definition_with_constructor_iterator = m_custom_element_definitions.find_if([&constructor](auto const& definition) {
|
||||
return definition->constructor().callback == constructor->callback;
|
||||
});
|
||||
|
||||
if (existing_definition_with_constructor_iterator != m_custom_element_definitions.end())
|
||||
return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "The given constructor is already in use by another custom element"_string));
|
||||
|
||||
// 5. Let localName be name.
|
||||
String local_name = name;
|
||||
|
||||
// 6. Let extends be the value of the extends member of options, or null if no such member exists.
|
||||
auto& extends = options.extends;
|
||||
|
||||
// 7. If extends is not null, then:
|
||||
if (extends.has_value()) {
|
||||
// 1. If extends is a valid custom element name, then throw a "NotSupportedError" DOMException.
|
||||
if (is_valid_custom_element_name(extends.value()))
|
||||
return JS::throw_completion(WebIDL::NotSupportedError::create(realm, MUST(String::formatted("'{}' is a custom element name, only non-custom elements can be extended"sv, extends.value()))));
|
||||
|
||||
// 2. If the element interface for extends and the HTML namespace is HTMLUnknownElement (e.g., if extends does not indicate an element definition in this specification), then throw a "NotSupportedError" DOMException.
|
||||
if (DOM::is_unknown_html_element(extends.value()))
|
||||
return JS::throw_completion(WebIDL::NotSupportedError::create(realm, MUST(String::formatted("'{}' is an unknown HTML element"sv, extends.value()))));
|
||||
|
||||
// 3. Set localName to extends.
|
||||
local_name = extends.value();
|
||||
}
|
||||
|
||||
// 8. If this CustomElementRegistry's element definition is running flag is set, then throw a "NotSupportedError" DOMException.
|
||||
if (m_element_definition_is_running)
|
||||
return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Cannot recursively define custom elements"_string));
|
||||
|
||||
// 9. Set this CustomElementRegistry's element definition is running flag.
|
||||
m_element_definition_is_running = true;
|
||||
|
||||
// 10. Let formAssociated be false.
|
||||
bool form_associated = false;
|
||||
|
||||
// 11. Let disableInternals be false.
|
||||
bool disable_internals = false;
|
||||
|
||||
// 12. Let disableShadow be false.
|
||||
bool disable_shadow = false;
|
||||
|
||||
// 13. Let observedAttributes be an empty sequence<DOMString>.
|
||||
Vector<String> observed_attributes;
|
||||
|
||||
// NOTE: This is not in the spec, but is required because of how we catch the exception by using a lambda, meaning we need to define this variable outside of it to use it later.
|
||||
CustomElementDefinition::LifecycleCallbacksStorage lifecycle_callbacks;
|
||||
|
||||
// 14. Run the following substeps while catching any exceptions:
|
||||
auto get_definition_attributes_from_constructor = [&]() -> JS::ThrowCompletionOr<void> {
|
||||
// 1. Let prototype be ? Get(constructor, "prototype").
|
||||
auto prototype_value = TRY(constructor->callback->get(vm.names.prototype));
|
||||
|
||||
// 2. If prototype is not an Object, then throw a TypeError exception.
|
||||
if (!prototype_value.is_object())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, prototype_value.to_string_without_side_effects());
|
||||
|
||||
auto& prototype = prototype_value.as_object();
|
||||
|
||||
// 3. Let lifecycleCallbacks be a map with the keys "connectedCallback", "disconnectedCallback", "adoptedCallback", and "attributeChangedCallback", each of which belongs to an entry whose value is null.
|
||||
lifecycle_callbacks.set(CustomElementReactionNames::connectedCallback, {});
|
||||
lifecycle_callbacks.set(CustomElementReactionNames::disconnectedCallback, {});
|
||||
lifecycle_callbacks.set(CustomElementReactionNames::adoptedCallback, {});
|
||||
lifecycle_callbacks.set(CustomElementReactionNames::attributeChangedCallback, {});
|
||||
|
||||
// 4. For each of the keys callbackName in lifecycleCallbacks, in the order listed in the previous step:
|
||||
for (auto const& callback_name : { CustomElementReactionNames::connectedCallback, CustomElementReactionNames::disconnectedCallback, CustomElementReactionNames::adoptedCallback, CustomElementReactionNames::attributeChangedCallback }) {
|
||||
// 1. Let callbackValue be ? Get(prototype, callbackName).
|
||||
auto callback_value = TRY(prototype.get(callback_name.to_deprecated_fly_string()));
|
||||
|
||||
// 2. If callbackValue is not undefined, then set the value of the entry in lifecycleCallbacks with key callbackName to the result of converting callbackValue to the Web IDL Function callback type. Rethrow any exceptions from the conversion.
|
||||
if (!callback_value.is_undefined()) {
|
||||
auto callback = TRY(convert_value_to_callback_function(vm, callback_value));
|
||||
lifecycle_callbacks.set(callback_name, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If the value of the entry in lifecycleCallbacks with key "attributeChangedCallback" is not null, then:
|
||||
auto attribute_changed_callback_iterator = lifecycle_callbacks.find(CustomElementReactionNames::attributeChangedCallback);
|
||||
VERIFY(attribute_changed_callback_iterator != lifecycle_callbacks.end());
|
||||
if (attribute_changed_callback_iterator->value) {
|
||||
// 1. Let observedAttributesIterable be ? Get(constructor, "observedAttributes").
|
||||
auto observed_attributes_iterable = TRY(constructor->callback->get(JS::PropertyKey { "observedAttributes" }));
|
||||
|
||||
// 2. If observedAttributesIterable is not undefined, then set observedAttributes to the result of converting observedAttributesIterable to a sequence<DOMString>. Rethrow any exceptions from the conversion.
|
||||
if (!observed_attributes_iterable.is_undefined())
|
||||
observed_attributes = TRY(convert_value_to_sequence_of_strings(vm, observed_attributes_iterable));
|
||||
}
|
||||
|
||||
// 6. Let disabledFeatures be an empty sequence<DOMString>.
|
||||
Vector<String> disabled_features;
|
||||
|
||||
// 7. Let disabledFeaturesIterable be ? Get(constructor, "disabledFeatures").
|
||||
auto disabled_features_iterable = TRY(constructor->callback->get(JS::PropertyKey { "disabledFeatures" }));
|
||||
|
||||
// 8. If disabledFeaturesIterable is not undefined, then set disabledFeatures to the result of converting disabledFeaturesIterable to a sequence<DOMString>. Rethrow any exceptions from the conversion.
|
||||
if (!disabled_features_iterable.is_undefined())
|
||||
disabled_features = TRY(convert_value_to_sequence_of_strings(vm, disabled_features_iterable));
|
||||
|
||||
// 9. Set disableInternals to true if disabledFeatures contains "internals".
|
||||
disable_internals = disabled_features.contains_slow("internals"sv);
|
||||
|
||||
// 10. Set disableShadow to true if disabledFeatures contains "shadow".
|
||||
disable_shadow = disabled_features.contains_slow("shadow"sv);
|
||||
|
||||
// 11. Let formAssociatedValue be ? Get( constructor, "formAssociated").
|
||||
auto form_associated_value = TRY(constructor->callback->get(JS::PropertyKey { "formAssociated" }));
|
||||
|
||||
// 12. Set formAssociated to the result of converting formAssociatedValue to a boolean. Rethrow any exceptions from the conversion.
|
||||
// NOTE: Converting to a boolean cannot throw with ECMAScript.
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-boolean
|
||||
// An ECMAScript value V is converted to an IDL boolean value by running the following algorithm:
|
||||
// 1. Let x be the result of computing ToBoolean(V).
|
||||
// 2. Return the IDL boolean value that is the one that represents the same truth value as the ECMAScript Boolean value x.
|
||||
form_associated = form_associated_value.to_boolean();
|
||||
|
||||
// 13. If formAssociated is true, for each of "formAssociatedCallback", "formResetCallback", "formDisabledCallback", and "formStateRestoreCallback" callbackName:
|
||||
if (form_associated) {
|
||||
for (auto const& callback_name : { CustomElementReactionNames::formAssociatedCallback, CustomElementReactionNames::formResetCallback, CustomElementReactionNames::formDisabledCallback, CustomElementReactionNames::formStateRestoreCallback }) {
|
||||
// 1. Let callbackValue be ? Get(prototype, callbackName).
|
||||
auto callback_value = TRY(prototype.get(callback_name.to_deprecated_fly_string()));
|
||||
|
||||
// 2. If callbackValue is not undefined, then set the value of the entry in lifecycleCallbacks with key callbackName to the result of converting callbackValue to the Web IDL Function callback type. Rethrow any exceptions from the conversion.
|
||||
if (!callback_value.is_undefined())
|
||||
lifecycle_callbacks.set(callback_name, TRY(convert_value_to_callback_function(vm, callback_value)));
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
auto maybe_exception = get_definition_attributes_from_constructor();
|
||||
|
||||
// Then, perform the following substep, regardless of whether the above steps threw an exception or not:
|
||||
// 1. Unset this CustomElementRegistry's element definition is running flag.
|
||||
m_element_definition_is_running = false;
|
||||
|
||||
// Finally, if the first set of substeps threw an exception, then rethrow that exception (thus terminating this algorithm). Otherwise, continue onward.
|
||||
if (maybe_exception.is_throw_completion())
|
||||
return maybe_exception.release_error();
|
||||
|
||||
// 15. Let definition be a new custom element definition with name name, local name localName, constructor constructor, observed attributes observedAttributes, lifecycle callbacks lifecycleCallbacks, form-associated formAssociated, disable internals disableInternals, and disable shadow disableShadow.
|
||||
auto definition = CustomElementDefinition::create(realm, name, local_name, *constructor, move(observed_attributes), move(lifecycle_callbacks), form_associated, disable_internals, disable_shadow);
|
||||
|
||||
// 16. Add definition to this CustomElementRegistry.
|
||||
m_custom_element_definitions.append(definition);
|
||||
|
||||
// 17. Let document be this CustomElementRegistry's relevant global object's associated Document.
|
||||
auto& document = verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document();
|
||||
|
||||
// 18. Let upgrade candidates be all elements that are shadow-including descendants of document, whose namespace is the HTML namespace and whose local name is localName, in shadow-including tree order.
|
||||
// Additionally, if extends is non-null, only include elements whose is value is equal to name.
|
||||
Vector<JS::Handle<DOM::Element>> upgrade_candidates;
|
||||
|
||||
document.for_each_shadow_including_descendant([&](DOM::Node& inclusive_descendant) {
|
||||
if (!is<DOM::Element>(inclusive_descendant))
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto& inclusive_descendant_element = static_cast<DOM::Element&>(inclusive_descendant);
|
||||
|
||||
if (inclusive_descendant_element.namespace_uri() == Namespace::HTML && inclusive_descendant_element.local_name() == local_name && (!extends.has_value() || inclusive_descendant_element.is_value() == name))
|
||||
upgrade_candidates.append(JS::make_handle(inclusive_descendant_element));
|
||||
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// 19. For each element element in upgrade candidates, enqueue a custom element upgrade reaction given element and definition.
|
||||
for (auto& element : upgrade_candidates)
|
||||
element->enqueue_a_custom_element_upgrade_reaction(definition);
|
||||
|
||||
// 20. If this CustomElementRegistry's when-defined promise map contains an entry with key name:
|
||||
auto promise_when_defined_iterator = m_when_defined_promise_map.find(name);
|
||||
if (promise_when_defined_iterator != m_when_defined_promise_map.end()) {
|
||||
// 1. Let promise be the value of that entry.
|
||||
auto promise = promise_when_defined_iterator->value;
|
||||
|
||||
// 2. Resolve promise with constructor.
|
||||
WebIDL::resolve_promise(realm, promise, constructor->callback);
|
||||
|
||||
// 3. Delete the entry with key name from this CustomElementRegistry's when-defined promise map.
|
||||
m_when_defined_promise_map.remove(name);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get
|
||||
Variant<JS::Handle<WebIDL::CallbackType>, JS::Value> CustomElementRegistry::get(String const& name) const
|
||||
{
|
||||
// 1. If this CustomElementRegistry contains an entry with name name, then return that entry's constructor.
|
||||
auto existing_definition_iterator = m_custom_element_definitions.find_if([&name](auto const& definition) {
|
||||
return definition->name() == name;
|
||||
});
|
||||
|
||||
if (!existing_definition_iterator.is_end())
|
||||
return JS::make_handle((*existing_definition_iterator)->constructor());
|
||||
|
||||
// 2. Otherwise, return undefined.
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-getname
|
||||
Optional<String> CustomElementRegistry::get_name(JS::Handle<WebIDL::CallbackType> const& constructor) const
|
||||
{
|
||||
// 1. If this CustomElementRegistry contains an entry with constructor constructor, then return that entry's name.
|
||||
auto existing_definition_iterator = m_custom_element_definitions.find_if([&constructor](auto const& definition) {
|
||||
return definition->constructor().callback == constructor.cell()->callback;
|
||||
});
|
||||
|
||||
if (!existing_definition_iterator.is_end())
|
||||
return (*existing_definition_iterator)->name();
|
||||
|
||||
// 2. Return null.
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-whendefined
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> CustomElementRegistry::when_defined(String const& name)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
||||
// 1. If name is not a valid custom element name, then return a new promise rejected with a "SyntaxError" DOMException.
|
||||
if (!is_valid_custom_element_name(name))
|
||||
return WebIDL::create_rejected_promise(realm, WebIDL::SyntaxError::create(realm, MUST(String::formatted("'{}' is not a valid custom element name"sv, name))));
|
||||
|
||||
// 2. If this CustomElementRegistry contains an entry with name name, then return a new promise resolved with that entry's constructor.
|
||||
auto existing_definition_iterator = m_custom_element_definitions.find_if([&name](JS::Handle<CustomElementDefinition> const& definition) {
|
||||
return definition->name() == name;
|
||||
});
|
||||
|
||||
if (existing_definition_iterator != m_custom_element_definitions.end())
|
||||
return WebIDL::create_resolved_promise(realm, (*existing_definition_iterator)->constructor().callback);
|
||||
|
||||
// 3. Let map be this CustomElementRegistry's when-defined promise map.
|
||||
// NOTE: Not necessary.
|
||||
|
||||
// 4. If map does not contain an entry with key name, create an entry in map with key name and whose value is a new promise.
|
||||
// 5. Let promise be the value of the entry in map with key name.
|
||||
JS::GCPtr<WebIDL::Promise> promise;
|
||||
|
||||
auto existing_promise_iterator = m_when_defined_promise_map.find(name);
|
||||
if (existing_promise_iterator != m_when_defined_promise_map.end()) {
|
||||
promise = existing_promise_iterator->value;
|
||||
} else {
|
||||
promise = WebIDL::create_promise(realm);
|
||||
m_when_defined_promise_map.set(name, *promise);
|
||||
}
|
||||
|
||||
// 5. Return promise.
|
||||
VERIFY(promise);
|
||||
return JS::NonnullGCPtr { *promise };
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-upgrade
|
||||
void CustomElementRegistry::upgrade(JS::NonnullGCPtr<DOM::Node> root) const
|
||||
{
|
||||
// 1. Let candidates be a list of all of root's shadow-including inclusive descendant elements, in shadow-including tree order.
|
||||
Vector<JS::Handle<DOM::Element>> candidates;
|
||||
|
||||
root->for_each_shadow_including_inclusive_descendant([&](DOM::Node& inclusive_descendant) {
|
||||
if (!is<DOM::Element>(inclusive_descendant))
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto& inclusive_descendant_element = static_cast<DOM::Element&>(inclusive_descendant);
|
||||
candidates.append(JS::make_handle(inclusive_descendant_element));
|
||||
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// 2. For each candidate of candidates, try to upgrade candidate.
|
||||
for (auto& candidate : candidates)
|
||||
candidate->try_to_upgrade();
|
||||
}
|
||||
|
||||
JS::GCPtr<CustomElementDefinition> CustomElementRegistry::get_definition_with_name_and_local_name(String const& name, String const& local_name) const
|
||||
{
|
||||
auto definition_iterator = m_custom_element_definitions.find_if([&](JS::Handle<CustomElementDefinition> const& definition) {
|
||||
return definition->name() == name && definition->local_name() == local_name;
|
||||
});
|
||||
|
||||
return definition_iterator.is_end() ? nullptr : definition_iterator->ptr();
|
||||
}
|
||||
|
||||
JS::GCPtr<CustomElementDefinition> CustomElementRegistry::get_definition_from_new_target(JS::FunctionObject const& new_target) const
|
||||
{
|
||||
auto definition_iterator = m_custom_element_definitions.find_if([&](JS::Handle<CustomElementDefinition> const& definition) {
|
||||
return definition->constructor().callback.ptr() == &new_target;
|
||||
});
|
||||
|
||||
return definition_iterator.is_end() ? nullptr : definition_iterator->ptr();
|
||||
}
|
||||
|
||||
}
|
54
Libraries/LibWeb/HTML/CustomElements/CustomElementRegistry.h
Normal file
54
Libraries/LibWeb/HTML/CustomElements/CustomElementRegistry.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/RefCounted.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct ElementDefinitionOptions {
|
||||
Optional<String> extends;
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#customelementregistry
|
||||
class CustomElementRegistry : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(CustomElementRegistry, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(CustomElementRegistry);
|
||||
|
||||
public:
|
||||
virtual ~CustomElementRegistry() override;
|
||||
|
||||
JS::ThrowCompletionOr<void> define(String const& name, WebIDL::CallbackType* constructor, ElementDefinitionOptions options);
|
||||
Variant<JS::Handle<WebIDL::CallbackType>, JS::Value> get(String const& name) const;
|
||||
Optional<String> get_name(JS::Handle<WebIDL::CallbackType> const& constructor) const;
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> when_defined(String const& name);
|
||||
void upgrade(JS::NonnullGCPtr<DOM::Node> root) const;
|
||||
|
||||
JS::GCPtr<CustomElementDefinition> get_definition_with_name_and_local_name(String const& name, String const& local_name) const;
|
||||
JS::GCPtr<CustomElementDefinition> get_definition_from_new_target(JS::FunctionObject const& new_target) const;
|
||||
|
||||
private:
|
||||
CustomElementRegistry(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
// Every CustomElementRegistry has a set of custom element definitions, initially empty. In general, algorithms in this specification look up elements in the registry by any of name, local name, or constructor.
|
||||
Vector<JS::NonnullGCPtr<CustomElementDefinition>> m_custom_element_definitions;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#element-definition-is-running
|
||||
// Every CustomElementRegistry also has an element definition is running flag which is used to prevent reentrant invocations of element definition. It is initially unset.
|
||||
bool m_element_definition_is_running { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#when-defined-promise-map
|
||||
// Every CustomElementRegistry also has a when-defined promise map, mapping valid custom element names to promises. It is used to implement the whenDefined() method.
|
||||
OrderedHashMap<String, JS::NonnullGCPtr<WebIDL::Promise>> m_when_defined_promise_map;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#import <DOM/Node.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/#customelementregistry
|
||||
[Exposed=Window]
|
||||
interface CustomElementRegistry {
|
||||
[CEReactions] undefined define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options = {});
|
||||
(CustomElementConstructor or undefined) get(DOMString name);
|
||||
DOMString? getName(CustomElementConstructor constructor);
|
||||
Promise<CustomElementConstructor> whenDefined(DOMString name);
|
||||
[CEReactions] undefined upgrade(Node root);
|
||||
};
|
||||
|
||||
callback CustomElementConstructor = HTMLElement ();
|
||||
|
||||
dictionary ElementDefinitionOptions {
|
||||
DOMString extends;
|
||||
};
|
83
Libraries/LibWeb/HTML/DOMParser.cpp
Normal file
83
Libraries/LibWeb/HTML/DOMParser.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/DOMParserPrototype.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/DOM/XMLDocument.h>
|
||||
#include <LibWeb/HTML/DOMParser.h>
|
||||
#include <LibWeb/HTML/HTMLDocument.h>
|
||||
#include <LibWeb/HTML/Parser/HTMLParser.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/XML/XMLDocumentBuilder.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(DOMParser);
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMParser>> DOMParser::construct_impl(JS::Realm& realm)
|
||||
{
|
||||
return realm.heap().allocate<DOMParser>(realm, realm);
|
||||
}
|
||||
|
||||
DOMParser::DOMParser(JS::Realm& realm)
|
||||
: PlatformObject(realm)
|
||||
{
|
||||
}
|
||||
|
||||
DOMParser::~DOMParser() = default;
|
||||
|
||||
void DOMParser::initialize(JS::Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(DOMParser);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring
|
||||
JS::NonnullGCPtr<DOM::Document> DOMParser::parse_from_string(StringView string, Bindings::DOMParserSupportedType type)
|
||||
{
|
||||
// FIXME: 1. Let compliantString to the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, string, "DOMParser parseFromString", and "script".
|
||||
|
||||
// 2. Let document be a new Document, whose content type is type and url is this's relevant global object's associated Document's URL.
|
||||
JS::GCPtr<DOM::Document> document;
|
||||
|
||||
// 3. Switch on type:
|
||||
if (type == Bindings::DOMParserSupportedType::Text_Html) {
|
||||
// -> "text/html"
|
||||
document = HTML::HTMLDocument::create(realm(), verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document().url());
|
||||
document->set_content_type(Bindings::idl_enum_to_string(type));
|
||||
|
||||
// 1. Parse HTML from a string given document and compliantString. FIXME: Use compliantString.
|
||||
document->parse_html_from_a_string(string);
|
||||
} else {
|
||||
// -> Otherwise
|
||||
document = DOM::XMLDocument::create(realm(), verify_cast<HTML::Window>(relevant_global_object(*this)).associated_document().url());
|
||||
document->set_content_type(Bindings::idl_enum_to_string(type));
|
||||
document->set_document_type(DOM::Document::Type::XML);
|
||||
|
||||
// 1. Create an XML parser parse, associated with document, and with XML scripting support disabled.
|
||||
XML::Parser parser(string, { .resolve_external_resource = resolve_xml_resource });
|
||||
XMLDocumentBuilder builder { *document, XMLScriptingSupport::Disabled };
|
||||
// 2. Parse compliantString using parser. FIXME: Use compliantString.
|
||||
auto result = parser.parse_with_listener(builder);
|
||||
// 3. If the previous step resulted in an XML well-formedness or XML namespace well-formedness error, then:
|
||||
if (result.is_error() || builder.has_error()) {
|
||||
// NOTE: The XML parsing can produce nodes before it hits an error, just remove them.
|
||||
// 1. Assert: document has no child nodes.
|
||||
document->remove_all_children(true);
|
||||
// 2. Let root be the result of creating an element given document, "parsererror", and "http://www.mozilla.org/newlayout/xml/parsererror.xml".
|
||||
auto root = DOM::create_element(*document, "parsererror"_fly_string, "http://www.mozilla.org/newlayout/xml/parsererror.xml"_fly_string).release_value_but_fixme_should_propagate_errors();
|
||||
// FIXME: 3. Optionally, add attributes or children to root to describe the nature of the parsing error.
|
||||
// 4. Append root to document.
|
||||
MUST(document->append_child(*root));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return document.
|
||||
return *document;
|
||||
}
|
||||
|
||||
}
|
35
Libraries/LibWeb/HTML/DOMParser.h
Normal file
35
Libraries/LibWeb/HTML/DOMParser.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Heap/GCPtr.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparser
|
||||
class DOMParser final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(DOMParser, Bindings::PlatformObject);
|
||||
JS_DECLARE_ALLOCATOR(DOMParser);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMParser>> construct_impl(JS::Realm&);
|
||||
|
||||
virtual ~DOMParser() override;
|
||||
|
||||
JS::NonnullGCPtr<DOM::Document> parse_from_string(StringView, Bindings::DOMParserSupportedType type);
|
||||
|
||||
private:
|
||||
explicit DOMParser(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
};
|
||||
|
||||
}
|
17
Libraries/LibWeb/HTML/DOMParser.idl
Normal file
17
Libraries/LibWeb/HTML/DOMParser.idl
Normal file
|
@ -0,0 +1,17 @@
|
|||
#import <DOM/Document.idl>
|
||||
|
||||
enum DOMParserSupportedType {
|
||||
"text/html",
|
||||
"text/xml",
|
||||
"application/xml",
|
||||
"application/xhtml+xml",
|
||||
"image/svg+xml"
|
||||
};
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparser
|
||||
[Exposed=Window]
|
||||
interface DOMParser {
|
||||
constructor();
|
||||
|
||||
Document parseFromString(DOMString string, DOMParserSupportedType type);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue