Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Unified Diff: ui/base/x/x11_window_cache.cc

Issue 2177823002: X11: Add window cache Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix test compilation Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/base/x/x11_window_cache.cc
diff --git a/ui/base/x/x11_window_cache.cc b/ui/base/x/x11_window_cache.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8496896f8445554d009800d2f1c97c5343c60b01
--- /dev/null
+++ b/ui/base/x/x11_window_cache.cc
@@ -0,0 +1,585 @@
+// Copyright 2016 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 "ui/base/x/x11_window_cache.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xlib-xcb.h>
+#include <xcb/xcbext.h>
+
+#include <algorithm>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "base/logging.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/events/platform/x11/x11_event_source.h"
+
+namespace ui {
+
+namespace {
+
+const char kNetWmIcon[] = "_NET_WM_ICON";
+
+// In case a property's value is huge, only cache the first 64KiB, and
+// indicate in Property that the cached value is not the entire value.
+static constexpr auto kMaxPropertySize = 0xffff;
+
+template <typename T>
+void CacheWindowGeometryFromResponse(XWindowCache::Window* window,
+ const T& response) {
+ window->x = response.x;
+ window->y = response.y;
+ window->width = response.width;
+ window->height = response.height;
+ window->border_width = response.border_width;
+}
+
+auto FindChild(XWindowCache::Window* parent, xcb_window_t child_id)
+ -> decltype(parent->children.begin()) {
+ return std::find_if(parent->children.begin(), parent->children.end(),
+ [child_id](std::unique_ptr<XWindowCache::Window>& child) {
+ return child->id == child_id;
+ });
+}
+
+} // anonymous namespace
+
+const XWindowCache::Property* XWindowCache::Window::GetProperty(
+ XID atom) const {
+ auto it = properties.find(atom);
+ return it == properties.end() ? nullptr : it->second.get();
+}
+
+struct XWindowCache::GetWindowAttributesRequest
+ : public X11EventSource::Request {
+ GetWindowAttributesRequest(Window* window, XWindowCache* cache)
+ : Request(
+ xcb_get_window_attributes(cache->connection_, window->id).sequence),
+ cache_(cache),
+ window_(window) {
+ window_->attributes_request = this;
+ }
+
+ void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
+ if (error) {
+ switch (error->error_code) {
+ case BadDrawable:
+ case BadWindow:
+ cache_->DestroyWindow(window_);
+ return;
+ default:
+ NOTREACHED();
+ }
+ }
+ auto* reply = reinterpret_cast<xcb_get_window_attributes_reply_t*>(r);
+
+ window_->attributes_request = nullptr;
+ window_->override_redirect = reply->override_redirect;
+ window_->is_mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED;
+ }
+
+ protected:
+ XWindowCache* cache_;
+ Window* window_;
+};
+
+struct XWindowCache::GetGeometryRequest : public X11EventSource::Request {
+ GetGeometryRequest(Window* window, XWindowCache* cache)
+ : Request(xcb_get_geometry(cache->connection_, window->id).sequence),
+ cache_(cache),
+ window_(window) {
+ window_->geometry_request = this;
+ }
+
+ void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
+ if (error) {
+ switch (error->error_code) {
+ case BadDrawable:
+ cache_->DestroyWindow(window_);
+ return;
+ default:
+ NOTREACHED();
+ }
+ }
+ auto* reply = reinterpret_cast<xcb_get_geometry_reply_t*>(r);
+
+ window_->geometry_request = nullptr;
+ CacheWindowGeometryFromResponse(window_, *reply);
+ }
+
+ protected:
+ XWindowCache* cache_;
+ Window* window_;
+};
+
+struct XWindowCache::ListPropertiesRequest : public X11EventSource::Request {
+ ListPropertiesRequest(Window* window, XWindowCache* cache)
+ : Request(xcb_list_properties(cache->connection_, window->id).sequence),
+ cache_(cache),
+ window_(window) {
+ window_->properties_request = this;
+ }
+
+ void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
+ if (error) {
+ switch (error->error_code) {
+ case BadWindow:
+ cache_->DestroyWindow(window_);
+ return;
+ default:
+ NOTREACHED();
+ }
+ }
+ auto* reply = reinterpret_cast<xcb_list_properties_reply_t*>(r);
+
+ window_->properties_request = nullptr;
+ for (int i = 0; i < xcb_list_properties_atoms_length(reply); i++) {
+ xcb_atom_t atom = xcb_list_properties_atoms(reply)[i];
+ cache_->CacheProperty(window_, atom);
+ }
+ }
+
+ protected:
+ XWindowCache* cache_;
+ Window* window_;
+};
+
+struct XWindowCache::QueryTreeRequest : public X11EventSource::Request {
+ QueryTreeRequest(Window* window, XWindowCache* cache)
+ : Request(xcb_query_tree(cache->connection_, window->id).sequence),
+ cache_(cache),
+ window_(window) {
+ window_->children_request = this;
+ }
+
+ void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
+ if (error) {
+ switch (error->error_code) {
+ case BadWindow:
+ cache_->DestroyWindow(window_);
+ return;
+ default:
+ NOTREACHED();
+ }
+ }
+ auto* reply = reinterpret_cast<xcb_query_tree_reply_t*>(r);
+
+ DCHECK(window_->children.empty());
+
+ xcb_window_t* children = xcb_query_tree_children(reply);
+ int n_children = xcb_query_tree_children_length(reply);
+
+ window_->children_request = nullptr;
+
+ // Iterate over children from top-to-bottom.
+ for (int i = 0; i < n_children; i++)
+ cache_->CreateWindow(children[i], window_);
+ }
+
+ protected:
+ XWindowCache* cache_;
+ Window* window_;
+};
+
+struct XWindowCache::GetPropertyRequest : public X11EventSource::Request {
+ GetPropertyRequest(Window* window,
+ Property* property,
+ xcb_atom_t atom,
+ XWindowCache* cache)
+ : Request(xcb_get_property(cache->connection_,
+ false,
+ window->id,
+ atom,
+ XCB_ATOM_ANY,
+ 0,
+ kMaxPropertySize)
+ .sequence),
+ cache_(cache),
+ window_(window),
+ property_(property),
+ atom_(atom) {
+ property->property_request = this;
+ }
+
+ void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
+ if (error) {
+ switch (error->error_code) {
+ case BadWindow:
+ cache_->DestroyWindow(window_);
+ return;
+ case BadValue:
+ case BadAtom:
+ default:
+ NOTREACHED();
+ }
+ }
+ auto* reply = reinterpret_cast<xcb_get_property_reply_t*>(r);
+
+ if (reply->format == 0) {
+ // According to Xlib, a format of anything other than 8, 16, or 32 is a
+ // BadImplementation error. However, this occurs when creating a new
+ // xterm window, so just forget about the property in question.
+ window_->properties.erase(atom_);
+ return;
+ }
+
+ property_->property_request = nullptr;
+ property_->type = reply->type;
+ DCHECK(reply->format == 8 || reply->format == 16 || reply->format == 32);
+ property_->data_format = reply->format;
+ property_->data_length = xcb_get_property_value_length(reply);
+ uint32_t data_bytes = property_->data_length * (property_->data_format / 8);
+ property_->data.bits_8 = new uint8_t[data_bytes];
+ std::memcpy(property_->data.bits_8, xcb_get_property_value(reply),
+ data_bytes);
+ }
+
+ protected:
+ XWindowCache* cache_;
+ Window* window_;
+ Property* property_;
+ xcb_atom_t atom_;
+};
+
+XWindowCache::Window::Window(xcb_window_t id,
+ XWindowCache::Window* parent,
+ XWindowCache* cache)
+ : id(id),
+ parent(parent),
+ attributes_request(nullptr),
+ geometry_request(nullptr),
+ properties_request(nullptr),
+ children_request(nullptr),
+ cache_(cache) {
+ uint32_t event_mask =
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
+ if (id == cache->root_id_) {
+ event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ }
+ // TODO(thomasanderson): Use XForeignWindowManager to select on the mask
+ // instead of resetting it.
+ auto cookie = xcb_change_window_attributes(cache->connection_, id,
+ XCB_CW_EVENT_MASK, &event_mask);
+ xcb_discard_reply(cache->connection_, cookie.sequence);
+
+ // Get the desired window state AFTER requesting state change events to avoid
+ // race conditions.
+
+ cache->event_source_->EnqueueRequest(new QueryTreeRequest(this, cache));
+ cache->event_source_->EnqueueRequest(new ListPropertiesRequest(this, cache));
+ cache->event_source_->EnqueueRequest(
+ new GetWindowAttributesRequest(this, cache));
+ cache->event_source_->EnqueueRequest(new GetGeometryRequest(this, cache));
+ xcb_flush(cache->connection_);
+}
+
+XWindowCache::Window::~Window() {
+ // The window tree was created top-down, so must be destroyed bottom-up.
+ while (!children.empty())
+ cache_->DestroyWindow(children.front().get());
+
+ if (attributes_request)
+ cache_->event_source_->DiscardRequest(attributes_request);
+ if (geometry_request)
+ cache_->event_source_->DiscardRequest(geometry_request);
+ if (properties_request)
+ cache_->event_source_->DiscardRequest(properties_request);
+ if (children_request)
+ cache_->event_source_->DiscardRequest(children_request);
+
+ // TODO(thomasanderson): Use XForeignWindowManager to deselect on the mask
+ // instead of resetting it.
+ uint32_t event_mask = XCB_EVENT_MASK_NO_EVENT;
+ auto cookie = xcb_change_window_attributes(cache_->connection_, id,
+ XCB_CW_EVENT_MASK, &event_mask);
+ // Window |id| may already be destroyed at this point, so the
+ // change_attributes request may give a BadWindow error. In this case, just
+ // ignore the error.
+ xcb_discard_reply(cache_->connection_, cookie.sequence);
+}
+
+XWindowCache::Property::Property(xcb_atom_t name,
+ Window* window,
+ XWindowCache* cache)
+ : name(name), property_request(nullptr), cache_(cache) {
+ data.bits_8 = nullptr;
+
+ cache->event_source_->EnqueueRequest(
+ new GetPropertyRequest(window, this, name, cache));
+ xcb_flush(cache->connection_);
+}
+
+XWindowCache::Property::~Property() {
+ if (property_request)
+ cache_->event_source_->DiscardRequest(property_request);
+
+ delete[] data.bits_8;
+}
+
+// Xlib shall own the event queue.
+XWindowCache::XWindowCache(XDisplay* display,
+ X11EventSource* event_source,
+ XID root)
+ : display_(display),
+ connection_(XGetXCBConnection(display_)),
+ root_id_(root),
+ event_source_(event_source),
+ root_(nullptr),
+ net_wm_icon_(0) {
+ DCHECK(event_source);
+ DCHECK(!xcb_connection_has_error(connection_));
+
+ if (PlatformEventSource::GetInstance())
+ PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
+
+ net_wm_icon_cookie_ =
+ xcb_intern_atom(connection_, false, sizeof(kNetWmIcon) - 1, kNetWmIcon);
+
+ CreateWindow(root, nullptr);
+}
+
+XWindowCache::~XWindowCache() {
+ if (PlatformEventSource::GetInstance())
+ PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
+}
+
+const XWindowCache::Window* XWindowCache::GetWindow(XID id) const {
+ return GetWindowInternal(id);
+}
+
+XWindowCache::Window* XWindowCache::GetWindowInternal(XID id) const {
+ auto it = windows_.find(id);
+ return it == windows_.end() ? nullptr : it->second;
+}
+
+// TODO(thomasanderson): Call ProcessEvent directly from X11EventSource. Have
+// ProcessEvent return an indication of if it is possible for clients other
+// than XWindowCache to be interested in the event, so that event dispatchers
+// don't get bogged down with the many events that XWindowCache selects.
+void XWindowCache::WillProcessEvent(const PlatformEvent& event) {
+ ProcessEvent(event);
+}
+
+void XWindowCache::DidProcessEvent(const PlatformEvent& event) {}
+
+void XWindowCache::ProcessEvent(const XEvent* e) {
+ switch (e->type) {
+ case PropertyNotify: {
+ Window* window = GetWindowInternal(e->xproperty.window);
+ if (!window)
+ break;
+
+ switch (e->xproperty.state) {
+ case PropertyDelete:
+ window->properties.erase(e->xproperty.atom);
+ break;
+ case PropertyNewValue:
+ CacheProperty(window, e->xproperty.atom);
+ break;
+ default:
+ NOTREACHED();
+ }
+ break;
+ }
+ case CirculateNotify: {
+ Window* window = GetWindowInternal(e->xcirculate.window);
+ if (!window)
+ break;
+
+ if (e->xcirculate.event == e->xcirculate.window)
+ break; // This is our root window
+
+ Window* parent = window->parent;
+ if (parent->id != e->xcirculate.event)
+ ResetCache();
+
+ auto it = FindChild(parent, window->id);
+ switch (e->xcirculate.place) {
+ case PlaceOnTop:
+ parent->children.push_front(std::move(*it));
+ break;
+ case PlaceOnBottom:
+ parent->children.push_back(std::move(*it));
+ break;
+ default:
+ NOTREACHED();
+ }
+ parent->children.erase(it);
+ break;
+ }
+ case ConfigureNotify: {
+ Window* window = GetWindowInternal(e->xconfigure.window);
+ if (!window)
+ break;
+
+ CacheWindowGeometryFromResponse(window, e->xconfigure);
+
+ if (e->xconfigure.event == e->xconfigure.window)
+ break;
+
+ Window* parent = window->parent;
+ if (parent->id != e->xconfigure.event)
+ ResetCache();
+
+ auto it = FindChild(parent, window->id);
+ if (e->xconfigure.above) {
+ auto it_above = FindChild(parent, e->xconfigure.above);
+ if (it == parent->children.end())
+ ResetCache();
+ else
+ parent->children.insert(it_above, std::move(*it));
+ } else {
+ // |window| is not above any other sibling window
+ parent->children.push_back(std::move(*it));
+ }
+ parent->children.erase(it);
+ break;
+ }
+ case CreateNotify: {
+ Window* parent = GetWindowInternal(e->xcreatewindow.parent);
+ if (!parent)
+ break;
+
+ if (parent->children_request) {
+ // |parent| is in the process of being cached, so we will pick up this
+ // window in the near future.
+ break;
+ }
+
+ CreateWindow(e->xcreatewindow.window, parent);
+ break;
+ }
+ case DestroyNotify: {
+ Window* window = GetWindowInternal(e->xdestroywindow.window);
+ if (!window)
+ break;
+ DestroyWindow(window);
+ break;
+ }
+ case GravityNotify: {
+ Window* window = GetWindowInternal(e->xgravity.window);
+ if (!window)
+ break;
+
+ window->x = e->xgravity.x;
+ window->y = e->xgravity.y;
+ break;
+ }
+ case MapNotify: {
+ Window* window = GetWindowInternal(e->xmap.window);
+ if (!window)
+ break;
+
+ window->override_redirect = e->xmap.override_redirect;
+ window->is_mapped = true;
+ break;
+ }
+ case ReparentNotify: {
+ Window* window = GetWindowInternal(e->xreparent.window);
+ if (!window)
+ break;
+
+ window->x = e->xreparent.x;
+ window->y = e->xreparent.y;
+
+ window->override_redirect = e->xreparent.override_redirect;
+ window->is_mapped = false; // Reparenting a window unmaps it
+
+ Window* old_parent = window->parent;
+ if (!old_parent)
+ break; // Don't worry about caching windows above our root.
+
+ Window* new_parent = GetWindowInternal(e->xreparent.parent);
+ if (!new_parent || new_parent->children_request) {
+ // |window| is either no longer in our tree, or we are already waiting
+ // to receive a list of |new_parent|'s children.
+ DestroyWindow(window);
+ break;
+ }
+ window->parent = new_parent;
+
+ auto it = FindChild(old_parent, window->id);
+ new_parent->children.push_front(std::move(*it));
+ old_parent->children.erase(it);
+ break;
+ }
+ case UnmapNotify: {
+ Window* window = GetWindowInternal(e->xunmap.window);
+ if (!window)
+ break;
+
+ window->is_mapped = false;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void XWindowCache::ResetCache() {
+ NOTREACHED();
+
+ // On release builds, try to fix our state.
+ ResetCacheImpl();
+}
+
+void XWindowCache::ResetCacheImpl() {
+ // TODO(thomasanderson): Log something in UMA.
+ if (root_) {
+ DestroyWindow(root_.get());
+ DCHECK(windows_.empty());
+ CreateWindow(root_id_, nullptr);
+ }
+}
+
+void XWindowCache::CacheProperty(XWindowCache::Window* window,
+ xcb_atom_t atom) {
+ if (!net_wm_icon_ && net_wm_icon_cookie_.sequence) {
+ auto reply =
+ xcb_intern_atom_reply(connection_, net_wm_icon_cookie_, nullptr);
+ if (reply) {
+ net_wm_icon_ = reply->atom;
+ free(reply);
+ }
+ net_wm_icon_cookie_.sequence = 0;
+ }
+ if (atom == net_wm_icon_)
+ return;
+ window->properties[atom].reset(new Property(atom, window, this));
+}
+
+void XWindowCache::CreateWindow(xcb_window_t id, XWindowCache::Window* parent) {
+ auto it = windows_.find(id);
+ if (it != windows_.end()) {
+ // We're already tracking window |id|.
+ return;
+ }
+
+ Window* window = new Window(id, parent, this);
+ windows_[id] = window;
+ if (parent) {
+ parent->children.emplace_front(window);
+ } else {
+ DCHECK(!root_);
+ root_.reset(window);
+ }
+}
+
+void XWindowCache::DestroyWindow(Window* window) {
+ DCHECK(window);
+ xcb_window_t id = window->id;
+ if (window->parent) {
+ auto it = FindChild(window->parent, window->id);
+ DCHECK(it != window->parent->children.end());
+ window->parent->children.erase(it);
+ } else {
+ DCHECK_EQ(window, root_.get());
+ root_.reset();
+ }
+ windows_.erase(id);
Daniel Erat 2016/09/06 20:35:58 are you leaking Window objects here? please use st
+}
+
+} // namespace ui

Powered by Google App Engine
This is Rietveld 408576698