1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "android_webview/browser/aw_devtools_delegate.h"
7 #include "android_webview/browser/in_process_view_renderer.h"
9 #include "base/json/json_writer.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/values.h"
12 #include "content/public/browser/android/devtools_auth.h"
13 #include "content/public/browser/devtools_http_handler.h"
14 #include "content/public/browser/web_contents.h"
15 #include "content/public/common/url_constants.h"
16 #include "net/socket/unix_domain_socket_posix.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "webkit/common/user_agent/user_agent_util.h"
21 const char kFrontEndURL
[] =
22 "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html";
23 const char kSocketNameFormat
[] = "webview_devtools_remote_%d";
26 namespace android_webview
{
28 AwDevToolsDelegate::AwDevToolsDelegate(content::BrowserContext
* browser_context
)
29 : browser_context_(browser_context
) {
30 devtools_http_handler_
= content::DevToolsHttpHandler::Start(
31 new net::UnixDomainSocketWithAbstractNamespaceFactory(
32 base::StringPrintf(kSocketNameFormat
, getpid()),
34 base::Bind(&content::CanUserConnectToDevTools
)),
35 base::StringPrintf(kFrontEndURL
,
36 webkit_glue::GetWebKitRevision().c_str()),
40 AwDevToolsDelegate::~AwDevToolsDelegate() {
43 void AwDevToolsDelegate::Stop() {
44 devtools_http_handler_
->Stop();
45 // WARNING: |this| has now been deleted by the method above.
48 std::string
AwDevToolsDelegate::GetDiscoveryPageHTML() {
49 // This is a temporary way of providing the list of inspectable WebViews.
50 // Since WebView doesn't have its own resources now, it doesn't seem
51 // reasonable to create a dedicated .pak file just for this temporary page.
55 "<title>WebView remote debugging</title>"
60 " var tabs_list_request = new XMLHttpRequest();"
61 " tabs_list_request.open("
62 " 'GET', '/json/list?t=' + new Date().getTime(), true);"
63 " tabs_list_request.onreadystatechange = onReady;"
64 " tabs_list_request.send();"
66 "function processItem(item) {"
67 " var result = JSON.parse(item.description);"
68 " result.debuggable = !!item.devtoolsFrontendUrl;"
69 " result.debugUrl = item.devtoolsFrontendUrl;"
70 " result.title = item.title;"
73 "function onReady() {"
74 " if(this.readyState == 4 && this.status == 200) {"
75 " if(this.response != null)"
76 " var responseJSON = JSON.parse(this.response);"
78 " for (var i = 0; i < responseJSON.length; ++i)"
79 " items.push(processItem(responseJSON[i]));"
81 " for (var i = 0; i < items.length; ++i)"
82 " displayView(items[i]);"
86 "function addColumn(row, text) {"
87 " var column = document.createElement('td');"
88 " column.innerText = text;"
89 " row.appendChild(column);"
91 "function cutTextIfNeeded(text, maxLen) {"
92 " return text.length <= maxLen ?"
93 " text : text.substr(0, maxLen) + '\u2026';"
95 "function displayView(item) {"
96 " var row = document.createElement('tr');"
97 " var column = document.createElement('td');"
99 " if (item.debuggable) {"
100 " frontend_ref = document.createElement('a');"
101 " frontend_ref.href = item.debugUrl;"
102 " frontend_ref.title = item.title;"
103 " frontend_ref.target = '_blank';"
104 " column.appendChild(frontend_ref);"
106 " frontend_ref = column;"
108 " var text = document.createElement('span');"
110 " text.innerText = cutTextIfNeeded(item.title, 64);"
112 " text.innerText = '(untitled)';"
114 " frontend_ref.appendChild(text);"
116 " var attached = item.attached ? (bits |= 1, 'Y') : 'N';"
117 " var visible = item.visible ? (bits |= 2, 'Y') : 'N';"
118 " var empty = item.empty ? 'Y' : (bits |= 4, 'N');"
119 " row.setAttribute('class', bits);"
120 " row.appendChild(column);"
121 " addColumn(row, attached);"
122 " addColumn(row, visible);"
123 " addColumn(row, empty);"
124 " addColumn(row, item.screenX + ', ' + item.screenY);"
126 " !item.empty ? (item.width + '\u00d7' + item.height) : '');"
127 " document.getElementById('items').appendChild(row);"
129 "function filter() {"
130 " var show_attached = document.getElementById('show_attached').checked;"
131 " var show_visible = document.getElementById('show_visible').checked;"
132 " var show_nonempty = document.getElementById('show_nonempty').checked;"
133 " var items = document.getElementById('items').childNodes;"
134 " for (var i = 0; i < items.length; i++) {"
135 " if (items[i].nodeName == 'TR') {"
136 " var mask = parseInt(items[i].getAttribute('class'));"
138 " if (show_attached) show &= ((mask & 1) != 0);"
139 " if (show_visible) show &= ((mask & 2) != 0);"
140 " if (show_nonempty) show &= ((mask & 4) != 0);"
142 " items[i].style.display = 'table-row';"
144 " items[i].style.display = 'none';"
149 "var refreshInterval = 0;"
150 "function toggleRefresh() {"
151 " clearInterval(refreshInterval);"
152 " var enabled = document.getElementById('refresh').checked;"
154 " var time = document.getElementById('refresh_time').value * 1000;"
155 " refreshInterval = setInterval(onLoad, time);"
159 " var items = document.getElementById('items');"
160 " for (var i = 0; i < items.childNodes.length; i++) {"
161 " items.removeChild(items.childNodes[i]);"
166 "<body onload='onLoad()'>"
167 " <div id='caption'>Inspectable WebViews</div>"
168 " <div><p>Only show:</p>"
170 " <input type='checkbox' id='show_attached' onclick='filter()'>"
172 " <input type='checkbox' id='show_visible' onclick='filter()'>"
174 " <input type='checkbox' id='show_nonempty' onclick='filter()'>"
176 " <input type='checkbox' id='refresh' onclick='toggleRefresh()'>"
177 " Auto refresh every "
178 " <input type='number' id='refresh_time' value='2' min='1' max='99'"
179 " onchange='toggleRefresh();' /> seconds<br/>"
183 " <tr><th style='width:200px; text-align:left;'>Title</th>"
184 " <th>Attached</th><th>Visible</th><th>Empty</th>"
185 " <th style='width:100px; text-align:left;'>Position</th>"
186 " <th style='width:100px; text-align:left;'>Size</th>"
188 " <tbody id='items'></tbody>"
195 bool AwDevToolsDelegate::BundlesFrontendResources() {
199 base::FilePath
AwDevToolsDelegate::GetDebugFrontendDir() {
200 return base::FilePath();
203 std::string
AwDevToolsDelegate::GetPageThumbnailData(const GURL
& url
) {
207 content::RenderViewHost
* AwDevToolsDelegate::CreateNewTarget() {
211 content::DevToolsHttpHandlerDelegate::TargetType
212 AwDevToolsDelegate::GetTargetType(content::RenderViewHost
*) {
213 return kTargetTypeTab
;
216 std::string
AwDevToolsDelegate::GetViewDescription(
217 content::RenderViewHost
* rvh
) {
218 content::WebContents
* web_contents
=
219 content::WebContents::FromRenderViewHost(rvh
);
220 if (!web_contents
) return "";
222 BrowserViewRenderer
* bvr
223 = InProcessViewRenderer::FromWebContents(web_contents
);
225 base::DictionaryValue description
;
226 description
.SetBoolean("attached", bvr
->IsAttachedToWindow());
227 description
.SetBoolean("visible", bvr
->IsVisible());
228 gfx::Rect screen_rect
= bvr
->GetScreenRect();
229 description
.SetInteger("screenX", screen_rect
.x());
230 description
.SetInteger("screenY", screen_rect
.y());
231 description
.SetBoolean("empty", screen_rect
.size().IsEmpty());
232 if (!screen_rect
.size().IsEmpty()) {
233 description
.SetInteger("width", screen_rect
.width());
234 description
.SetInteger("height", screen_rect
.height());
237 base::JSONWriter::Write(&description
, &json
);
241 scoped_refptr
<net::StreamListenSocket
>
242 AwDevToolsDelegate::CreateSocketForTethering(
243 net::StreamListenSocket::Delegate
* delegate
,
248 } // namespace android_webview