#include "inspectable_web_contents_view_linux.h" #include #include #include "base/strings/stringprintf.h" #include "browser/browser_client.h" #include "browser/inspectable_web_contents_impl.h" #include "content/public/browser/web_contents_view.h" namespace brightray { InspectableWebContentsView* CreateInspectableContentsView(InspectableWebContentsImpl* inspectable_web_contents) { return new InspectableWebContentsViewLinux(inspectable_web_contents); } InspectableWebContentsViewLinux::InspectableWebContentsViewLinux(InspectableWebContentsImpl* inspectable_web_contents) : inspectable_web_contents_(inspectable_web_contents), devtools_window(NULL) { } InspectableWebContentsViewLinux::~InspectableWebContentsViewLinux() { if (devtools_window) gtk_widget_destroy(devtools_window); } #if 0 // some utility functions to debug GTK window hierarchies static void dump_one(GtkWidget *wat, int indent) { GtkAllocation alloc; gtk_widget_get_allocation(wat, &alloc); fprintf(stderr, "%*s[%p] %s @%d,%d %dx%d", indent, "", wat, g_type_name_from_instance((GTypeInstance*)wat), alloc.x, alloc.y, alloc.width, alloc.height); if (GTK_IS_WINDOW(wat)) fprintf(stderr, " - \"%s\"", gtk_window_get_title(GTK_WINDOW(wat))); fputc('\n', stderr); } static void dump_the_whole_tree(GtkWidget *wat, int indent) { if (!wat) { fprintf(stderr, "(nil)\n"); return; } dump_one(wat, indent); GList *kids = gtk_container_get_children(GTK_CONTAINER(wat)); for (GList *p=kids; p; p=p->next) { dump_the_whole_tree(GTK_WIDGET(p->data), indent+2); } } static void dump_parents(GtkWidget *wat) { fprintf(stderr, "Parents:\n"); for (GtkWidget *p=gtk_widget_get_parent(wat); p; p=gtk_widget_get_parent(p)) { dump_one(p, 2); } } #endif gfx::NativeView InspectableWebContentsViewLinux::GetNativeView() const { return inspectable_web_contents_->GetWebContents()->GetView()->GetNativeView(); } /* This code is a little bit hairy. The dev tools can be in any one of five places: 1. Unassigned and invisible. This is the default state until someone asks to 'inspect element' for the first time. In this case, devtools->parent is NULL. 2. In an onscreen window, visible. 3. In the bottom half of a GtkVPaned. 4. In the right half of a GtkHPaned. 5. In an offscreen window, invisible. This is where they go once they have been displayed and the user asks to "close" them. They can't be put back into the unassigned state. ShowDevTools() and is responsible for transitioning from any one of these states to the three visible states, 2-4, as indicated by the contents of the 'dockside' variable. The helper functions ShowDevToolsInWindow and ShowDevToolsInPane focus on transitioning to states 2 and 3+4, respectively. These helper functions are responsible for the entire transition, including cleaning up any extraneous containers from the old state. Hiding the dev tools is taken care of by CloseDevTools (from paned states 3+4 to invisible state 5) or by the "delete-event" signal on the devtools_window (from window state 2 to 5). Remember that GTK does reference counting, so a view with no refs and no parent will be freed. Views that have a ref but no parents will lose their dimensions. So it's best to move the devtools view from place to place with gtk_widget_reparent whenever possible. Unfortunately, one cannot reparent things into a GtkPaned, so fairly brittle use of g_object_[un]ref and gtk_container_remove happens. */ void InspectableWebContentsViewLinux::ShowDevTools() { GtkWidget *devtools = inspectable_web_contents()->devtools_web_contents()->GetView()->GetNativeView(); GtkWidget *parent = gtk_widget_get_parent(devtools); DLOG(INFO) << base::StringPrintf("InspectableWebContentsViewLinux::ShowDevTools - parent=%s@%p window=%p dockside=\"%s\"", g_type_name_from_instance((GTypeInstance*)parent), parent, devtools_window, dockside.c_str()); if (!parent || GTK_IS_PANED(parent)) { if (dockside == "undocked") ShowDevToolsInWindow(); else if (dockside == "bottom") ShowDevToolsInPane(true); else if (dockside == "right") ShowDevToolsInPane(false); } else { DCHECK(parent == devtools_window); if (dockside == "undocked") gtk_widget_show_all(parent); else if (dockside == "bottom") ShowDevToolsInPane(true); else if (dockside == "right") ShowDevToolsInPane(false); } } void InspectableWebContentsViewLinux::CloseDevTools() { GtkWidget *devtools = inspectable_web_contents()->devtools_web_contents()->GetView()->GetNativeView(); GtkWidget *parent = gtk_widget_get_parent(devtools); DLOG(INFO) << base::StringPrintf("InspectableWebContentsViewLinux::CloseDevTools - parent=%s@%p window=%p dockside=\"%s\"", g_type_name_from_instance((GTypeInstance*)parent), parent, devtools_window, dockside.c_str()); if (!parent) { return; // Not visible -> nothing to do } else if (GTK_IS_PANED(parent)) { GtkWidget *browser = GetBrowserWindow(); GtkWidget *view = GetNativeView(); if (!devtools_window) MakeDevToolsWindow(); gtk_widget_reparent(devtools, devtools_window); g_object_ref(parent); gtk_container_remove(GTK_CONTAINER(browser), parent); gtk_widget_reparent(view, browser); g_object_unref(parent); } else { DCHECK(parent == devtools_window); gtk_widget_hide(parent); } } bool InspectableWebContentsViewLinux::SetDockSide(const std::string& side) { DLOG(INFO) << "InspectableWebContentsViewLinux::SetDockSide: \"" << side << "\""; if (side != "undocked" && side != "bottom" && side != "right") return false; // unsupported display location if (dockside == side) return true; // no change from current location dockside = side; // If devtools already has a parent, then we're being asked to move it. GtkWidget *devtools = inspectable_web_contents()->devtools_web_contents()->GetView()->GetNativeView(); if (gtk_widget_get_parent(devtools)) { ShowDevTools(); } return true; } void InspectableWebContentsViewLinux::ShowDevToolsInWindow() { GtkWidget *devtools = inspectable_web_contents()->devtools_web_contents()->GetView()->GetNativeView(); GtkWidget *parent = gtk_widget_get_parent(devtools); if (!devtools_window) MakeDevToolsWindow(); if (!parent) { gtk_container_add(GTK_CONTAINER(devtools_window), devtools); } else if (parent != devtools_window) { DCHECK(GTK_IS_PANED(parent)); gtk_widget_reparent(devtools, devtools_window); // Remove the pane. GtkWidget *view = GetNativeView(); GtkWidget *browser = GetBrowserWindow(); g_object_ref(view); gtk_container_remove(GTK_CONTAINER(parent), view); gtk_container_remove(GTK_CONTAINER(browser), parent); gtk_container_add(GTK_CONTAINER(browser), view); g_object_unref(view); } gtk_widget_show_all(devtools_window); } void InspectableWebContentsViewLinux::MakeDevToolsWindow() { DCHECK(!devtools_window); devtools_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(devtools_window), "Developer Tools"); gtk_window_set_default_size(GTK_WINDOW(devtools_window), 800, 600); g_signal_connect(GTK_OBJECT(devtools_window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), this); } void InspectableWebContentsViewLinux::ShowDevToolsInPane(bool on_bottom) { GtkWidget *devtools = inspectable_web_contents()->devtools_web_contents()->GetView()->GetNativeView(); GtkWidget *parent = gtk_widget_get_parent(devtools); GtkWidget *pane = on_bottom ? gtk_vpaned_new() : gtk_hpaned_new(); GtkWidget *view = GetNativeView(); GtkWidget *browser = GetBrowserWindow(); GtkAllocation alloc; gtk_widget_get_allocation(browser, &alloc); gtk_paned_set_position(GTK_PANED(pane), on_bottom ? alloc.height*2/3 : alloc.width/2); if (!parent) { g_object_ref(view); gtk_container_remove(GTK_CONTAINER(browser), view); gtk_paned_add1(GTK_PANED(pane), view); gtk_paned_add2(GTK_PANED(pane), devtools); g_object_unref(view); } else if (GTK_IS_PANED(parent)) { g_object_ref(view); g_object_ref(devtools); gtk_container_remove(GTK_CONTAINER(parent), view); gtk_container_remove(GTK_CONTAINER(parent), devtools); gtk_paned_add1(GTK_PANED(pane), view); gtk_paned_add2(GTK_PANED(pane), devtools); g_object_unref(view); g_object_unref(devtools); gtk_container_remove(GTK_CONTAINER(browser), parent); } else { DCHECK(parent == devtools_window); g_object_ref(view); gtk_container_remove(GTK_CONTAINER(devtools_window), devtools); gtk_container_remove(GTK_CONTAINER(browser), view); gtk_paned_add1(GTK_PANED(pane), view); gtk_paned_add2(GTK_PANED(pane), devtools); g_object_unref(view); gtk_widget_hide(devtools_window); } gtk_container_add(GTK_CONTAINER(browser), pane); gtk_widget_show_all(pane); } GtkWidget *InspectableWebContentsViewLinux::GetBrowserWindow() { GtkWidget *view = GetNativeView(); GtkWidget *parent = gtk_widget_get_parent(view); GtkWidget *browser = GTK_IS_PANED(parent) ? gtk_widget_get_parent(parent) : parent; DCHECK(GTK_IS_WINDOW(browser)); return browser; } }