WebPageProxy::close() is a no-op for terminated processes
[WebKit-https.git] / Source / WebKit2 / UIProcess / WebInspectorProxy.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  * Portions Copyright (c) 2011 Motorola Mobility, Inc.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "WebInspectorProxy.h"
29
30 #if ENABLE(INSPECTOR)
31
32 #include "APIURLRequest.h"
33 #include "WebFramePolicyListenerProxy.h"
34 #include "WebFrameProxy.h"
35 #include "WebInspectorMessages.h"
36 #include "WebInspectorProxyMessages.h"
37 #include "WebPageCreationParameters.h"
38 #include "WebPageGroup.h"
39 #include "WebPageProxy.h"
40 #include "WebPreferences.h"
41 #include "WebProcessProxy.h"
42 #include <WebCore/SchemeRegistry.h>
43 #include <wtf/NeverDestroyed.h>
44 #include <wtf/text/StringBuilder.h>
45
46 #if ENABLE(INSPECTOR_SERVER)
47 #include "WebInspectorServer.h"
48 #endif
49
50 using namespace WebCore;
51
52 namespace WebKit {
53
54 const unsigned WebInspectorProxy::minimumWindowWidth = 750;
55 const unsigned WebInspectorProxy::minimumWindowHeight = 400;
56
57 const unsigned WebInspectorProxy::initialWindowWidth = 1000;
58 const unsigned WebInspectorProxy::initialWindowHeight = 650;
59
60 const unsigned WebInspectorProxy::minimumAttachedWidth = 750;
61 const unsigned WebInspectorProxy::minimumAttachedHeight = 250;
62
63 class WebInspectorPageGroups {
64 public:
65     static WebInspectorPageGroups& shared()
66     {
67         static NeverDestroyed<WebInspectorPageGroups> instance;
68         return instance;
69     }
70
71     unsigned inspectorLevel(WebPageGroup& inspectedPageGroup)
72     {
73         return isInspectorPageGroup(inspectedPageGroup) ? inspectorPageGroupLevel(inspectedPageGroup) + 1 : 1;
74     }
75
76     bool isInspectorPageGroup(WebPageGroup& group)
77     {
78         return m_pageGroupLevel.contains(&group);
79     }
80
81     unsigned inspectorPageGroupLevel(WebPageGroup& group)
82     {
83         ASSERT(isInspectorPageGroup(group));
84         return m_pageGroupLevel.get(&group);
85     }
86
87     WebPageGroup* inspectorPageGroupForLevel(unsigned level)
88     {
89         // The level is the key of the HashMap, so it cannot be 0.
90         ASSERT(level);
91
92         auto iterator = m_pageGroupByLevel.find(level);
93         if (iterator != m_pageGroupByLevel.end())
94             return iterator->value.get();
95
96         RefPtr<WebPageGroup> group = createInspectorPageGroup(level);
97         m_pageGroupByLevel.set(level, group.get());
98         m_pageGroupLevel.set(group.get(), level);
99         return group.get();
100     }
101
102 private:
103     static PassRefPtr<WebPageGroup> createInspectorPageGroup(unsigned level)
104     {
105         RefPtr<WebPageGroup> pageGroup = WebPageGroup::create(String::format("__WebInspectorPageGroupLevel%u__", level), false, false);
106
107 #ifndef NDEBUG
108         // Allow developers to inspect the Web Inspector in debug builds.
109         pageGroup->preferences().setDeveloperExtrasEnabled(true);
110         pageGroup->preferences().setLogsPageMessagesToSystemConsoleEnabled(true);
111 #endif
112
113         pageGroup->preferences().setApplicationChromeModeEnabled(true);
114
115         return pageGroup.release();
116     }
117
118     typedef HashMap<unsigned, RefPtr<WebPageGroup> > PageGroupByLevelMap;
119     typedef HashMap<WebPageGroup*, unsigned> PageGroupLevelMap;
120
121     PageGroupByLevelMap m_pageGroupByLevel;
122     PageGroupLevelMap m_pageGroupLevel;
123 };
124
125 WebInspectorProxy::WebInspectorProxy(WebPageProxy* page)
126     : m_page(page)
127     , m_isVisible(false)
128     , m_isAttached(false)
129     , m_isDebuggingJavaScript(false)
130     , m_isProfilingJavaScript(false)
131     , m_isProfilingPage(false)
132     , m_showMessageSent(false)
133     , m_createdInspectorPage(false)
134     , m_ignoreFirstBringToFront(false)
135     , m_attachmentSide(AttachmentSideBottom)
136 #if PLATFORM(GTK) || PLATFORM(EFL)
137     , m_inspectorView(0)
138     , m_inspectorWindow(0)
139 #endif
140 #if ENABLE(INSPECTOR_SERVER)
141     , m_remoteInspectionPageId(0)
142 #endif
143 {
144     m_level = WebInspectorPageGroups::shared().inspectorLevel(m_page->pageGroup());
145     m_page->process().addMessageReceiver(Messages::WebInspectorProxy::messageReceiverName(), m_page->pageID(), *this);
146 }
147
148 WebInspectorProxy::~WebInspectorProxy()
149 {
150 }
151
152 WebPageGroup* WebInspectorProxy::inspectorPageGroup() const
153 {
154     return WebInspectorPageGroups::shared().inspectorPageGroupForLevel(m_level);
155 }
156
157 void WebInspectorProxy::invalidate()
158 {
159 #if ENABLE(INSPECTOR_SERVER)
160     if (m_remoteInspectionPageId)
161         WebInspectorServer::shared().unregisterPage(m_remoteInspectionPageId);
162 #endif
163
164     m_page->process().removeMessageReceiver(Messages::WebInspectorProxy::messageReceiverName(), m_page->pageID());
165
166     didClose();
167
168     m_page = 0;
169 }
170
171 // Public APIs
172 bool WebInspectorProxy::isFront()
173 {
174     if (!m_page)
175         return false;
176
177     return platformIsFront();
178 }
179
180 void WebInspectorProxy::connect()
181 {
182     if (!m_page)
183         return;
184
185     if (m_showMessageSent)
186         return;
187
188     m_showMessageSent = true;
189     m_ignoreFirstBringToFront = true;
190
191     m_page->process().send(Messages::WebInspector::Show(), m_page->pageID());
192 }
193
194 void WebInspectorProxy::show()
195 {
196     if (!m_page)
197         return;
198
199     if (isConnected()) {
200         bringToFront();
201         return;
202     }
203
204     connect();
205
206     // Don't ignore the first bringToFront so it opens the Inspector.
207     m_ignoreFirstBringToFront = false;
208 }
209
210 void WebInspectorProxy::hide()
211 {
212     if (!m_page)
213         return;
214
215     m_isVisible = false;
216
217     platformHide();
218 }
219
220 void WebInspectorProxy::close()
221 {
222     if (!m_page)
223         return;
224
225     m_page->process().send(Messages::WebInspector::Close(), m_page->pageID());
226
227     didClose();
228 }
229
230 void WebInspectorProxy::showConsole()
231 {
232     if (!m_page)
233         return;
234
235     m_page->process().send(Messages::WebInspector::ShowConsole(), m_page->pageID());
236 }
237
238 void WebInspectorProxy::showResources()
239 {
240     if (!m_page)
241         return;
242
243     m_page->process().send(Messages::WebInspector::ShowResources(), m_page->pageID());
244 }
245
246 void WebInspectorProxy::showMainResourceForFrame(WebFrameProxy* frame)
247 {
248     if (!m_page)
249         return;
250
251     m_page->process().send(Messages::WebInspector::ShowMainResourceForFrame(frame->frameID()), m_page->pageID());
252 }
253
254 void WebInspectorProxy::attachBottom()
255 {
256     attach(AttachmentSideBottom);
257 }
258
259 void WebInspectorProxy::attachRight()
260 {
261     attach(AttachmentSideRight);
262 }
263
264 void WebInspectorProxy::attach(AttachmentSide side)
265 {
266     if (!m_page || !canAttach())
267         return;
268
269     m_isAttached = true;
270     m_attachmentSide = side;
271
272     inspectorPageGroup()->preferences().setInspectorAttachmentSide(side);
273
274     if (m_isVisible)
275         inspectorPageGroup()->preferences().setInspectorStartsAttached(true);
276
277     switch (m_attachmentSide) {
278     case AttachmentSideBottom:
279         m_page->process().send(Messages::WebInspector::AttachedBottom(), m_page->pageID());
280         break;
281
282     case AttachmentSideRight:
283         m_page->process().send(Messages::WebInspector::AttachedRight(), m_page->pageID());
284         break;
285     }
286
287     platformAttach();
288 }
289
290 void WebInspectorProxy::detach()
291 {
292     if (!m_page)
293         return;
294
295     m_isAttached = false;
296
297     if (m_isVisible)
298         inspectorPageGroup()->preferences().setInspectorStartsAttached(false);
299
300     m_page->process().send(Messages::WebInspector::Detached(), m_page->pageID());
301
302     platformDetach();
303 }
304
305 void WebInspectorProxy::setAttachedWindowHeight(unsigned height)
306 {
307     inspectorPageGroup()->preferences().setInspectorAttachedHeight(height);
308     platformSetAttachedWindowHeight(height);
309 }
310
311 void WebInspectorProxy::setAttachedWindowWidth(unsigned width)
312 {
313     inspectorPageGroup()->preferences().setInspectorAttachedWidth(width);
314     platformSetAttachedWindowWidth(width);
315 }
316
317 void WebInspectorProxy::toggleJavaScriptDebugging()
318 {
319     if (!m_page)
320         return;
321
322     if (m_isDebuggingJavaScript)
323         m_page->process().send(Messages::WebInspector::StopJavaScriptDebugging(), m_page->pageID());
324     else
325         m_page->process().send(Messages::WebInspector::StartJavaScriptDebugging(), m_page->pageID());
326
327     // FIXME: have the WebProcess notify us on state changes.
328     m_isDebuggingJavaScript = !m_isDebuggingJavaScript;
329 }
330
331 void WebInspectorProxy::toggleJavaScriptProfiling()
332 {
333     if (!m_page)
334         return;
335
336     if (m_isProfilingJavaScript)
337         m_page->process().send(Messages::WebInspector::StopJavaScriptProfiling(), m_page->pageID());
338     else
339         m_page->process().send(Messages::WebInspector::StartJavaScriptProfiling(), m_page->pageID());
340
341     // FIXME: have the WebProcess notify us on state changes.
342     m_isProfilingJavaScript = !m_isProfilingJavaScript;
343 }
344
345 void WebInspectorProxy::togglePageProfiling()
346 {
347     if (!m_page)
348         return;
349
350     if (m_isProfilingPage)
351         m_page->process().send(Messages::WebInspector::StopPageProfiling(), m_page->pageID());
352     else
353         m_page->process().send(Messages::WebInspector::StartPageProfiling(), m_page->pageID());
354
355     // FIXME: have the WebProcess notify us on state changes.
356     m_isProfilingPage = !m_isProfilingPage;
357 }
358
359 bool WebInspectorProxy::isInspectorPage(WebPageProxy& page)
360 {
361     return WebInspectorPageGroups::shared().isInspectorPageGroup(page.pageGroup());
362 }
363
364 static bool isMainOrTestInspectorPage(const WebInspectorProxy* webInspectorProxy, WKURLRequestRef requestRef)
365 {
366     URL requestURL(URL(), toImpl(requestRef)->resourceRequest().url());
367     if (!WebCore::SchemeRegistry::shouldTreatURLSchemeAsLocal(requestURL.protocol()))
368         return false;
369
370     // Use URL so we can compare just the paths.
371     URL mainPageURL(URL(), webInspectorProxy->inspectorPageURL());
372     ASSERT(WebCore::SchemeRegistry::shouldTreatURLSchemeAsLocal(mainPageURL.protocol()));
373     if (decodeURLEscapeSequences(requestURL.path()) == decodeURLEscapeSequences(mainPageURL.path()))
374         return true;
375
376     // We might not have a Test URL in Production builds.
377     String testPageURLString = webInspectorProxy->inspectorTestPageURL();
378     if (testPageURLString.isNull())
379         return false;
380
381     URL testPageURL(URL(), webInspectorProxy->inspectorTestPageURL());
382     ASSERT(WebCore::SchemeRegistry::shouldTreatURLSchemeAsLocal(testPageURL.protocol()));
383     return decodeURLEscapeSequences(requestURL.path()) == decodeURLEscapeSequences(testPageURL.path());
384 }
385
386 static void decidePolicyForNavigationAction(WKPageRef, WKFrameRef frameRef, WKFrameNavigationType, WKEventModifiers, WKEventMouseButton, WKFrameRef, WKURLRequestRef requestRef, WKFramePolicyListenerRef listenerRef, WKTypeRef, const void* clientInfo)
387 {
388     // Allow non-main frames to navigate anywhere.
389     if (!toImpl(frameRef)->isMainFrame()) {
390         toImpl(listenerRef)->use();
391         return;
392     }
393
394     const WebInspectorProxy* webInspectorProxy = static_cast<const WebInspectorProxy*>(clientInfo);
395     ASSERT(webInspectorProxy);
396
397     // Allow loading of the main inspector file.
398     if (isMainOrTestInspectorPage(webInspectorProxy, requestRef)) {
399         toImpl(listenerRef)->use();
400         return;
401     }
402
403     // Prevent everything else from loading in the inspector's page.
404     toImpl(listenerRef)->ignore();
405
406     // And instead load it in the inspected page.
407     webInspectorProxy->page()->loadRequest(toImpl(requestRef)->resourceRequest());
408 }
409
410 #if ENABLE(INSPECTOR_SERVER)
411 void WebInspectorProxy::enableRemoteInspection()
412 {
413     if (!m_remoteInspectionPageId)
414         m_remoteInspectionPageId = WebInspectorServer::shared().registerPage(this);
415 }
416
417 void WebInspectorProxy::remoteFrontendConnected()
418 {
419     m_page->process().send(Messages::WebInspector::RemoteFrontendConnected(), m_page->pageID());
420 }
421
422 void WebInspectorProxy::remoteFrontendDisconnected()
423 {
424     m_page->process().send(Messages::WebInspector::RemoteFrontendDisconnected(), m_page->pageID());
425 }
426
427 void WebInspectorProxy::dispatchMessageFromRemoteFrontend(const String& message)
428 {
429     m_page->process().send(Messages::WebInspector::DispatchMessageFromRemoteFrontend(message), m_page->pageID());
430 }
431 #endif
432
433 // Called by WebInspectorProxy messages
434 void WebInspectorProxy::createInspectorPage(uint64_t& inspectorPageID, WebPageCreationParameters& inspectorPageParameters)
435 {
436     inspectorPageID = 0;
437
438     if (!m_page)
439         return;
440
441     m_isAttached = shouldOpenAttached();
442     m_attachmentSide = static_cast<AttachmentSide>(inspectorPageGroup()->preferences().inspectorAttachmentSide());
443
444     WebPageProxy* inspectorPage = platformCreateInspectorPage();
445     ASSERT(inspectorPage);
446     if (!inspectorPage)
447         return;
448
449     inspectorPageID = inspectorPage->pageID();
450     inspectorPageParameters = inspectorPage->creationParameters();
451
452     WKPagePolicyClientV1 policyClient = {
453         { 1, this },
454         0, /* decidePolicyForNavigationAction_deprecatedForUseWithV0 */
455         0, /* decidePolicyForNewWindowAction */
456         0, /* decidePolicyForResponse_deprecatedForUseWithV0 */
457         0, /* unableToImplementPolicy */
458         decidePolicyForNavigationAction,
459         0, /* decidePolicyForResponse */
460     };
461
462     WKPageSetPagePolicyClient(toAPI(inspectorPage), &policyClient.base);
463
464     StringBuilder url;
465
466     url.append(inspectorPageURL());
467
468     url.appendLiteral("?dockSide=");
469
470     if (m_isAttached) {
471         switch (m_attachmentSide) {
472         case AttachmentSideBottom:
473             url.appendLiteral("bottom");
474             m_page->process().send(Messages::WebInspector::AttachedBottom(), m_page->pageID());
475             break;
476         case AttachmentSideRight:
477             url.appendLiteral("right");
478             m_page->process().send(Messages::WebInspector::AttachedRight(), m_page->pageID());
479             break;
480         }
481     } else
482         url.appendLiteral("undocked");
483
484     m_page->process().assumeReadAccessToBaseURL(inspectorBaseURL());
485
486     inspectorPage->loadRequest(URL(URL(), url.toString()));
487
488     m_createdInspectorPage = true;
489 }
490
491 void WebInspectorProxy::createInspectorPageForTest(uint64_t& inspectorPageID, WebPageCreationParameters& inspectorPageParameters)
492 {
493     inspectorPageID = 0;
494
495     if (!m_page)
496         return;
497
498     m_isAttached = false;
499
500     WebPageProxy* inspectorPage = platformCreateInspectorPage();
501     ASSERT(inspectorPage);
502     if (!inspectorPage)
503         return;
504
505     inspectorPageID = inspectorPage->pageID();
506     inspectorPageParameters = inspectorPage->creationParameters();
507
508     WKPagePolicyClientV1 policyClient = {
509         { 1, this },
510         0, /* decidePolicyForNavigationAction_deprecatedForUseWithV0 */
511         0, /* decidePolicyForNewWindowAction */
512         0, /* decidePolicyForResponse_deprecatedForUseWithV0 */
513         0, /* unableToImplementPolicy */
514         decidePolicyForNavigationAction,
515         0, /* decidePolicyForResponse */
516     };
517
518     WKPageSetPagePolicyClient(toAPI(inspectorPage), &policyClient.base);
519
520     m_page->process().assumeReadAccessToBaseURL(inspectorBaseURL());
521
522     inspectorPage->loadRequest(URL(URL(), inspectorTestPageURL()));
523
524     m_createdInspectorPage = true;
525 }
526
527 void WebInspectorProxy::open()
528 {
529     m_isVisible = true;
530
531     platformOpen();
532 }
533
534 void WebInspectorProxy::didClose()
535 {
536     if (!m_createdInspectorPage)
537         return;
538
539     m_isVisible = false;
540     m_isDebuggingJavaScript = false;
541     m_isProfilingJavaScript = false;
542     m_isProfilingPage = false;
543     m_createdInspectorPage = false;
544     m_showMessageSent = false;
545     m_ignoreFirstBringToFront = false;
546
547     if (m_isAttached)
548         platformDetach();
549     m_isAttached = false;
550
551     platformDidClose();
552 }
553
554 void WebInspectorProxy::bringToFront()
555 {
556     // WebCore::InspectorFrontendClientLocal tells us to do this on load. We want to
557     // ignore it once if we only wanted to connect. This allows the Inspector to later
558     // request to be brought to the front when a breakpoint is hit or some other action.
559     if (m_ignoreFirstBringToFront) {
560         m_ignoreFirstBringToFront = false;
561         return;
562     }
563
564     if (m_isVisible)
565         platformBringToFront();
566     else
567         open();
568 }
569
570 void WebInspectorProxy::attachAvailabilityChanged(bool available)
571 {
572     platformAttachAvailabilityChanged(available);
573 }
574
575 void WebInspectorProxy::inspectedURLChanged(const String& urlString)
576 {
577     platformInspectedURLChanged(urlString);
578 }
579
580 void WebInspectorProxy::save(const String& filename, const String& content, bool base64Encoded, bool forceSaveAs)
581 {
582     platformSave(filename, content, base64Encoded, forceSaveAs);
583 }
584
585 void WebInspectorProxy::append(const String& filename, const String& content)
586 {
587     platformAppend(filename, content);
588 }
589
590 bool WebInspectorProxy::canAttach()
591 {
592     // Keep this in sync with InspectorFrontendClientLocal::canAttachWindow. There are two implementations
593     // to make life easier in the multi-process world we have. WebInspectorProxy uses canAttach to decide if
594     // we can attach on open (on the UI process side). And InspectorFrontendClientLocal::canAttachWindow is
595     // used to decide if we can attach when the attach button is pressed (on the WebProcess side).
596
597     // If we are already attached, allow attaching again to allow switching sides.
598     if (m_isAttached)
599         return true;
600
601     // Don't allow attaching to another inspector -- two inspectors in one window is too much!
602     if (m_level > 1)
603         return false;
604
605     // Don't allow the attach if the window would be too small to accommodate the minimum inspector height.
606     unsigned inspectedPageHeight = platformInspectedWindowHeight();
607     unsigned inspectedPageWidth = platformInspectedWindowWidth();
608     unsigned maximumAttachedHeight = inspectedPageHeight * 3 / 4;
609     return minimumAttachedHeight <= maximumAttachedHeight && minimumAttachedWidth <= inspectedPageWidth;
610 }
611
612 bool WebInspectorProxy::shouldOpenAttached()
613 {
614     return inspectorPageGroup()->preferences().inspectorStartsAttached() && canAttach();
615 }
616
617 #if ENABLE(INSPECTOR_SERVER)
618 void WebInspectorProxy::sendMessageToRemoteFrontend(const String& message)
619 {
620     ASSERT(m_remoteInspectionPageId);
621     WebInspectorServer::shared().sendMessageOverConnection(m_remoteInspectionPageId, message);
622 }
623 #endif
624
625 } // namespace WebKit
626
627 #endif // ENABLE(INSPECTOR)