blob: 3ea9769e4a507db04f28fdd98365d34dbf33003f [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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/modules/screen_enumeration/screen_details.h"
#include "base/containers/contains.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
#include "third_party/blink/renderer/modules/screen_enumeration/screen_detailed.h"
#include "ui/display/screen_info.h"
namespace blink {
ScreenDetails::ScreenDetails(LocalDOMWindow* window)
: ExecutionContextLifecycleObserver(window) {
LocalFrame* frame = window->GetFrame();
const auto& screen_infos = frame->GetChromeClient().GetScreenInfos(*frame);
// Do not dispatch change events during class initialization.
UpdateScreenInfosImpl(window, screen_infos, /*dispatch_events=*/false);
}
const HeapVector<Member<ScreenDetailed>>& ScreenDetails::screens() const {
return screens_;
}
ScreenDetailed* ScreenDetails::currentScreen() const {
if (!DomWindow())
return nullptr;
if (screens_.IsEmpty())
return nullptr;
auto* it = base::ranges::find(screens_, current_display_id_,
&ScreenDetailed::DisplayId);
DCHECK(it != screens_.end());
return *it;
}
const AtomicString& ScreenDetails::InterfaceName() const {
return event_target_names::kScreenDetails;
}
ExecutionContext* ScreenDetails::GetExecutionContext() const {
return ExecutionContextLifecycleObserver::GetExecutionContext();
}
void ScreenDetails::ContextDestroyed() {
screens_.clear();
}
void ScreenDetails::Trace(Visitor* visitor) const {
visitor->Trace(screens_);
EventTargetWithInlineData::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
void ScreenDetails::UpdateScreenInfos(LocalDOMWindow* window,
const display::ScreenInfos& new_infos) {
UpdateScreenInfosImpl(window, new_infos, /*dispatch_events=*/true);
}
void ScreenDetails::UpdateScreenInfosImpl(LocalDOMWindow* window,
const display::ScreenInfos& new_infos,
bool dispatch_events) {
// Expect that all updates contain a non-zero set of screens.
DCHECK(!new_infos.screen_infos.empty());
// (1) Detect if screens were added or removed and update web exposed data.
bool added_or_removed = false;
// O(displays) should be small, so O(n^2) check in both directions
// instead of keeping some more efficient cache of display ids.
// Check if any screens have been removed and remove them from `screens_`.
for (WTF::wtf_size_t i = 0; i < screens_.size();
/*conditionally incremented*/) {
if (base::Contains(new_infos.screen_infos, screens_[i]->DisplayId(),
&display::ScreenInfo::display_id)) {
++i;
} else {
WillRemoveScreen(*screens_[i]);
screens_.EraseAt(i);
added_or_removed = true;
// Recheck this index.
}
}
// Check if any screens have been added, and append them to `screens_`.
for (const auto& info : new_infos.screen_infos) {
if (!base::Contains(screens_, info.display_id,
&ScreenDetailed::DisplayId)) {
screens_.push_back(MakeGarbageCollected<ScreenDetailed>(
window, info.display_id, info.is_internal,
GetNewLabelIdx(info.is_internal)));
added_or_removed = true;
}
}
// Sort `screens_` by position; x first and then y.
base::ranges::stable_sort(screens_, [](ScreenDetailed* a, ScreenDetailed* b) {
if (a->left() != b->left())
return a->left() < b->left();
return a->top() < b->top();
});
// Update current_display_id_ for currentScreen() before event dispatch.
current_display_id_ = new_infos.current_display_id;
// (2) At this point, all web exposed data is updated.
// `screens_` has the updated set of screens.
// `current_display_id_` has the updated value.
// (prior to this function) individual ScreenDetailed objects have new values.
//
// Enqueue events for dispatch if `dispatch_events` is true.
// Enqueuing event dispatch avoids recursion if screen changes occur while an
// event handler is running a nested event loop, e.g. via window.print().
if (dispatch_events) {
// Enqueue a change event if the current screen has changed.
if (prev_screen_infos_.screen_infos.empty() ||
prev_screen_infos_.current().display_id !=
new_infos.current().display_id ||
!ScreenDetailed::AreWebExposedScreenDetailedPropertiesEqual(
prev_screen_infos_.current(), new_infos.current())) {
EnqueueEvent(*Event::Create(event_type_names::kCurrentscreenchange),
TaskType::kMiscPlatformAPI);
}
// Enqueue a change event if screens were added or removed.
if (added_or_removed) {
// Allow fullscreen requests shortly after user-generated screens changes.
// TODO(enne): consider doing this only when screens have been added.
window->GetFrame()->ActivateTransientAllowFullscreen();
EnqueueEvent(*Event::Create(event_type_names::kScreenschange),
TaskType::kMiscPlatformAPI);
}
// Enqueue change events for any individual screens that changed.
// It's not guaranteed that screen_infos are ordered, so for each screen
// find the info that corresponds to it in old_info and new_infos.
for (Member<ScreenDetailed>& screen : screens_) {
auto id = screen->DisplayId();
auto new_it = base::ranges::find(new_infos.screen_infos, id,
&display::ScreenInfo::display_id);
DCHECK(new_it != new_infos.screen_infos.end());
auto old_it = base::ranges::find(prev_screen_infos_.screen_infos, id,
&display::ScreenInfo::display_id);
if (old_it != prev_screen_infos_.screen_infos.end() &&
!ScreenDetailed::AreWebExposedScreenDetailedPropertiesEqual(
*old_it, *new_it)) {
screen->EnqueueEvent(*Event::Create(event_type_names::kChange),
TaskType::kMiscPlatformAPI);
}
}
}
// (3) Store screen infos for change comparison next time.
//
// Aside: Because ScreenDetailed is a "live" thin wrapper over the ScreenInfo
// object owned by WidgetBase, WidgetBase's copy needs to be updated
// in UpdateSurfaceAndScreenInfo prior to this UpdateScreenInfos call so that
// when the events are fired, the live data is not stale. Therefore, this
// class needs to hold onto the "previous" info so that it knows which pieces
// of data have changed, as at a higher level the old data has already been
// rewritten with the new.
prev_screen_infos_ = new_infos;
}
uint32_t ScreenDetails::GetNewLabelIdx(bool is_internal) {
auto& set = is_internal ? internal_label_ids_ : external_label_ids_;
uint32_t label_idx = 1;
// This is O(n^2) but number of displays is very small.
while (true) {
if (!set.Contains(label_idx)) {
set.insert(label_idx);
return label_idx;
}
label_idx++;
}
}
void ScreenDetails::WillRemoveScreen(const ScreenDetailed& screen) {
auto& set =
screen.label_is_internal() ? internal_label_ids_ : external_label_ids_;
set.erase(screen.label_idx());
}
} // namespace blink