blob: 084364725c92cbfa30373a63f7d23a507cbb36ae [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/resize_observer/resize_observer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_resize_observer_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_resize_observer_options.h"
#include "third_party/blink/renderer/core/core_probes_inl.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/resize_observer/resize_observation.h"
#include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.h"
#include "third_party/blink/renderer/core/resize_observer/resize_observer_entry.h"
namespace blink {
constexpr const char* kBoxOptionBorderBox = "border-box";
constexpr const char* kBoxOptionContentBox = "content-box";
constexpr const char* kBoxOptionDevicePixelContentBox =
"device-pixel-content-box";
ResizeObserver* ResizeObserver::Create(ScriptState* script_state,
V8ResizeObserverCallback* callback) {
return MakeGarbageCollected<ResizeObserver>(
callback, LocalDOMWindow::From(script_state));
}
ResizeObserver* ResizeObserver::Create(LocalDOMWindow* window,
Delegate* delegate) {
return MakeGarbageCollected<ResizeObserver>(delegate, window);
}
ResizeObserver::ResizeObserver(V8ResizeObserverCallback* callback,
LocalDOMWindow* window)
: ActiveScriptWrappable<ResizeObserver>({}),
ExecutionContextClient(window),
callback_(callback),
skipped_observations_(false) {
DCHECK(callback_);
if (window) {
controller_ = ResizeObserverController::From(*window);
controller_->AddObserver(*this);
}
}
ResizeObserver::ResizeObserver(Delegate* delegate, LocalDOMWindow* window)
: ActiveScriptWrappable<ResizeObserver>({}),
ExecutionContextClient(window),
delegate_(delegate),
skipped_observations_(false) {
DCHECK(delegate_);
if (window) {
controller_ = ResizeObserverController::From(*window);
controller_->AddObserver(*this);
}
}
ResizeObserverBoxOptions ResizeObserver::ParseBoxOptions(
const String& box_options) {
if (box_options == kBoxOptionBorderBox)
return ResizeObserverBoxOptions::kBorderBox;
if (box_options == kBoxOptionContentBox)
return ResizeObserverBoxOptions::kContentBox;
if (box_options == kBoxOptionDevicePixelContentBox)
return ResizeObserverBoxOptions::kDevicePixelContentBox;
return ResizeObserverBoxOptions::kContentBox;
}
void ResizeObserver::observeInternal(Element* target,
ResizeObserverBoxOptions box_option) {
auto& observer_map = target->EnsureResizeObserverData();
if (observer_map.Contains(this)) {
auto observation = observer_map.find(this);
if ((*observation).value->ObservedBox() == box_option)
return;
// Unobserve target if box_option has changed and target already existed. If
// there is an existing observation of a different box, this new observation
// takes precedence. See:
// https://drafts.csswg.org/resize-observer/#processing-model
observations_.erase((*observation).value);
auto index = active_observations_.Find((*observation).value);
if (index != kNotFound) {
active_observations_.EraseAt(index);
}
observer_map.erase(observation);
}
auto* observation =
MakeGarbageCollected<ResizeObservation>(target, this, box_option);
observations_.insert(observation);
observer_map.Set(this, observation);
if (LocalFrameView* frame_view = target->GetDocument().View())
frame_view->ScheduleAnimation();
}
void ResizeObserver::observe(Element* target,
const ResizeObserverOptions* options) {
ResizeObserverBoxOptions box_option = ParseBoxOptions(options->box());
observeInternal(target, box_option);
}
void ResizeObserver::observe(Element* target) {
observeInternal(target, ResizeObserverBoxOptions::kContentBox);
}
void ResizeObserver::unobserve(Element* target) {
auto* observer_map = target ? target->ResizeObserverData() : nullptr;
if (!observer_map)
return;
auto observation = observer_map->find(this);
if (observation != observer_map->end()) {
observations_.erase((*observation).value);
auto index = active_observations_.Find((*observation).value);
if (index != kNotFound) {
active_observations_.EraseAt(index);
}
observer_map->erase(observation);
}
}
void ResizeObserver::disconnect() {
ObservationList observations;
observations_.Swap(observations);
for (auto& observation : observations) {
Element* target = (*observation).Target();
if (target)
target->EnsureResizeObserverData().erase(this);
}
ClearObservations();
}
size_t ResizeObserver::GatherObservations(size_t deeper_than) {
DCHECK(active_observations_.empty());
size_t min_observed_depth = ResizeObserverController::kDepthBottom;
for (auto& observation : observations_) {
if (!observation->ObservationSizeOutOfSync())
continue;
auto depth = observation->TargetDepth();
if (depth > deeper_than) {
active_observations_.push_back(*observation);
min_observed_depth = std::min(min_observed_depth, depth);
} else {
skipped_observations_ = true;
}
}
return min_observed_depth;
}
void ResizeObserver::DeliverObservations() {
if (active_observations_.empty())
return;
HeapVector<Member<ResizeObserverEntry>> entries;
for (auto& observation : active_observations_) {
// In case that the observer and the target belong to different execution
// contexts and the target's execution context is already gone, then skip
// such a target.
Element* target = observation->Target();
if (!target)
continue;
ExecutionContext* execution_context = target->GetExecutionContext();
if (!execution_context || execution_context->IsContextDestroyed())
continue;
observation->SetObservationSize(observation->ComputeTargetSize());
auto* entry =
MakeGarbageCollected<ResizeObserverEntry>(observation->Target());
entries.push_back(entry);
}
if (entries.size() == 0) {
// No entry to report.
// Note that, if |active_observations_| is not empty but |entries| is empty,
// it means that it's possible that no target element is making |callback_|
// alive. In this case, we must not touch |callback_|.
ClearObservations();
return;
}
DCHECK(callback_ || delegate_);
if (callback_) {
callback_->InvokeAndReportException(this, entries, this);
}
if (delegate_)
delegate_->OnResize(entries);
ClearObservations();
}
void ResizeObserver::ClearObservations() {
active_observations_.clear();
skipped_observations_ = false;
}
bool ResizeObserver::HasPendingActivity() const {
return !active_observations_.empty();
}
void ResizeObserver::Trace(Visitor* visitor) const {
visitor->Trace(callback_);
visitor->Trace(delegate_);
visitor->Trace(observations_);
visitor->Trace(active_observations_);
visitor->Trace(controller_);
ScriptWrappable::Trace(visitor);
ExecutionContextClient::Trace(visitor);
}
} // namespace blink