Web Inspector: Network: add button to show system certificate dialog
[WebKit-https.git] / Source / WebCore / inspector / InspectorFrontendHost.cpp
1 /*
2  * Copyright (C) 2007-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
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  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "InspectorFrontendHost.h"
32
33 #include "CertificateInfo.h"
34 #include "ContextMenu.h"
35 #include "ContextMenuController.h"
36 #include "ContextMenuItem.h"
37 #include "ContextMenuProvider.h"
38 #include "DOMWrapperWorld.h"
39 #include "Document.h"
40 #include "Editor.h"
41 #include "Event.h"
42 #include "FocusController.h"
43 #include "Frame.h"
44 #include "HitTestResult.h"
45 #include "InspectorController.h"
46 #include "InspectorFrontendClient.h"
47 #include "JSDOMConvertInterface.h"
48 #include "JSDOMExceptionHandling.h"
49 #include "JSExecState.h"
50 #include "JSInspectorFrontendHost.h"
51 #include "MouseEvent.h"
52 #include "Node.h"
53 #include "Page.h"
54 #include "Pasteboard.h"
55 #include "ScriptState.h"
56 #include "UserGestureIndicator.h"
57 #include <JavaScriptCore/ScriptFunctionCall.h>
58 #include <pal/system/Sound.h>
59 #include <wtf/StdLibExtras.h>
60 #include <wtf/text/Base64.h>
61
62 namespace WebCore {
63
64 using namespace Inspector;
65
66 #if ENABLE(CONTEXT_MENUS)
67 class FrontendMenuProvider : public ContextMenuProvider {
68 public:
69     static Ref<FrontendMenuProvider> create(InspectorFrontendHost* frontendHost, Deprecated::ScriptObject frontendApiObject, const Vector<ContextMenuItem>& items)
70     {
71         return adoptRef(*new FrontendMenuProvider(frontendHost, frontendApiObject, items));
72     }
73     
74     void disconnect()
75     {
76         m_frontendApiObject = { };
77         m_frontendHost = nullptr;
78     }
79     
80 private:
81     FrontendMenuProvider(InspectorFrontendHost* frontendHost, Deprecated::ScriptObject frontendApiObject, const Vector<ContextMenuItem>& items)
82         : m_frontendHost(frontendHost)
83         , m_frontendApiObject(frontendApiObject)
84         , m_items(items)
85     {
86     }
87
88     virtual ~FrontendMenuProvider()
89     {
90         contextMenuCleared();
91     }
92     
93     void populateContextMenu(ContextMenu* menu) override
94     {
95         for (auto& item : m_items)
96             menu->appendItem(item);
97     }
98     
99     void contextMenuItemSelected(ContextMenuAction action, const String&) override
100     {
101         if (m_frontendHost) {
102             UserGestureIndicator gestureIndicator(ProcessingUserGesture);
103             int itemNumber = action - ContextMenuItemBaseCustomTag;
104
105             Deprecated::ScriptFunctionCall function(m_frontendApiObject, "contextMenuItemSelected", WebCore::functionCallHandlerFromAnyThread);
106             function.appendArgument(itemNumber);
107             function.call();
108         }
109     }
110     
111     void contextMenuCleared() override
112     {
113         if (m_frontendHost) {
114             Deprecated::ScriptFunctionCall function(m_frontendApiObject, "contextMenuCleared", WebCore::functionCallHandlerFromAnyThread);
115             function.call();
116
117             m_frontendHost->m_menuProvider = nullptr;
118         }
119         m_items.clear();
120     }
121
122     InspectorFrontendHost* m_frontendHost;
123     Deprecated::ScriptObject m_frontendApiObject;
124     Vector<ContextMenuItem> m_items;
125 };
126 #endif
127
128 InspectorFrontendHost::InspectorFrontendHost(InspectorFrontendClient* client, Page* frontendPage)
129     : m_client(client)
130     , m_frontendPage(frontendPage)
131 #if ENABLE(CONTEXT_MENUS)
132     , m_menuProvider(nullptr)
133 #endif
134 {
135 }
136
137 InspectorFrontendHost::~InspectorFrontendHost()
138 {
139     ASSERT(!m_client);
140 }
141
142 void InspectorFrontendHost::disconnectClient()
143 {
144     m_client = nullptr;
145 #if ENABLE(CONTEXT_MENUS)
146     if (m_menuProvider)
147         m_menuProvider->disconnect();
148 #endif
149     m_frontendPage = nullptr;
150 }
151
152 void InspectorFrontendHost::addSelfToGlobalObjectInWorld(DOMWrapperWorld& world)
153 {
154     auto& state = *execStateFromPage(world, m_frontendPage);
155     auto& vm = state.vm();
156     JSC::JSLockHolder lock(vm);
157     auto scope = DECLARE_CATCH_SCOPE(vm);
158
159     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject());
160     globalObject.putDirect(vm, JSC::Identifier::fromString(&vm, "InspectorFrontendHost"), toJS<IDLInterface<InspectorFrontendHost>>(state, globalObject, *this));
161     if (UNLIKELY(scope.exception()))
162         reportException(&state, scope.exception());
163 }
164
165 void InspectorFrontendHost::loaded()
166 {
167     if (m_client)
168         m_client->frontendLoaded();
169 }
170
171 void InspectorFrontendHost::requestSetDockSide(const String& side)
172 {
173     if (!m_client)
174         return;
175     if (side == "undocked")
176         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Undocked);
177     else if (side == "right")
178         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Right);
179     else if (side == "left")
180         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Left);
181     else if (side == "bottom")
182         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Bottom);
183 }
184
185 void InspectorFrontendHost::closeWindow()
186 {
187     if (m_client) {
188         m_client->closeWindow();
189         disconnectClient(); // Disconnect from client.
190     }
191 }
192
193 void InspectorFrontendHost::bringToFront()
194 {
195     if (m_client)
196         m_client->bringToFront();
197 }
198
199 void InspectorFrontendHost::inspectedURLChanged(const String& newURL)
200 {
201     if (m_client)
202         m_client->inspectedURLChanged(newURL);
203 }
204
205 void InspectorFrontendHost::setZoomFactor(float zoom)
206 {
207     if (m_frontendPage)
208         m_frontendPage->mainFrame().setPageAndTextZoomFactors(zoom, 1);
209 }
210
211 float InspectorFrontendHost::zoomFactor()
212 {
213     if (m_frontendPage)
214         return m_frontendPage->mainFrame().pageZoomFactor();
215
216     return 1.0;
217 }
218
219 String InspectorFrontendHost::userInterfaceLayoutDirection()
220 {
221     if (m_client && m_client->userInterfaceLayoutDirection() == UserInterfaceLayoutDirection::RTL)
222         return "rtl"_s;
223
224     return "ltr"_s;
225 }
226
227 void InspectorFrontendHost::setAttachedWindowHeight(unsigned height)
228 {
229     if (m_client)
230         m_client->changeAttachedWindowHeight(height);
231 }
232
233 void InspectorFrontendHost::setAttachedWindowWidth(unsigned width)
234 {
235     if (m_client)
236         m_client->changeAttachedWindowWidth(width);
237 }
238
239 void InspectorFrontendHost::startWindowDrag()
240 {
241     if (m_client)
242         m_client->startWindowDrag();
243 }
244
245 void InspectorFrontendHost::moveWindowBy(float x, float y) const
246 {
247     if (m_client)
248         m_client->moveWindowBy(x, y);
249 }
250
251 String InspectorFrontendHost::localizedStringsURL()
252 {
253     return m_client ? m_client->localizedStringsURL() : String();
254 }
255
256 String InspectorFrontendHost::backendCommandsURL()
257 {
258     return m_client ? m_client->backendCommandsURL() : String();
259 }
260
261 String InspectorFrontendHost::debuggableType()
262 {
263     return m_client ? m_client->debuggableType() : String();
264 }
265
266 unsigned InspectorFrontendHost::inspectionLevel()
267 {
268     return m_client ? m_client->inspectionLevel() : 1;
269 }
270
271 String InspectorFrontendHost::platform()
272 {
273 #if PLATFORM(MAC) || PLATFORM(IOS_FAMILY)
274     return "mac"_s;
275 #elif OS(WINDOWS)
276     return "windows"_s;
277 #elif OS(LINUX)
278     return "linux"_s;
279 #elif OS(FREEBSD)
280     return "freebsd"_s;
281 #elif OS(OPENBSD)
282     return "openbsd"_s;
283 #else
284     return "unknown"_s;
285 #endif
286 }
287
288 String InspectorFrontendHost::port()
289 {
290 #if PLATFORM(GTK)
291     return "gtk"_s;
292 #else
293     return "unknown"_s;
294 #endif
295 }
296
297 void InspectorFrontendHost::copyText(const String& text)
298 {
299     Pasteboard::createForCopyAndPaste()->writePlainText(text, Pasteboard::CannotSmartReplace);
300 }
301
302 void InspectorFrontendHost::killText(const String& text, bool shouldPrependToKillRing, bool shouldStartNewSequence)
303 {
304     if (!m_frontendPage)
305         return;
306
307     Editor& editor = m_frontendPage->focusController().focusedOrMainFrame().editor();
308     editor.setStartNewKillRingSequence(shouldStartNewSequence);
309     Editor::KillRingInsertionMode insertionMode = shouldPrependToKillRing ? Editor::KillRingInsertionMode::PrependText : Editor::KillRingInsertionMode::AppendText;
310     editor.addTextToKillRing(text, insertionMode);
311 }
312
313 void InspectorFrontendHost::openInNewTab(const String& url)
314 {
315     if (WebCore::protocolIsJavaScript(url))
316         return;
317
318     if (m_client)
319         m_client->openInNewTab(url);
320 }
321
322 bool InspectorFrontendHost::canSave()
323 {
324     if (m_client)
325         return m_client->canSave();
326     return false;
327 }
328
329 void InspectorFrontendHost::save(const String& url, const String& content, bool base64Encoded, bool forceSaveAs)
330 {
331     if (m_client)
332         m_client->save(url, content, base64Encoded, forceSaveAs);
333 }
334
335 void InspectorFrontendHost::append(const String& url, const String& content)
336 {
337     if (m_client)
338         m_client->append(url, content);
339 }
340
341 void InspectorFrontendHost::close(const String&)
342 {
343 }
344
345 void InspectorFrontendHost::sendMessageToBackend(const String& message)
346 {
347     if (m_client)
348         m_client->sendMessageToBackend(message);
349 }
350
351 #if ENABLE(CONTEXT_MENUS)
352
353 static void populateContextMenu(Vector<InspectorFrontendHost::ContextMenuItem>&& items, ContextMenu& menu)
354 {
355     for (auto& item : items) {
356         if (item.type == "separator") {
357             menu.appendItem({ SeparatorType, ContextMenuItemTagNoAction, { } });
358             continue;
359         }
360
361         if (item.type == "subMenu" && item.subItems) {
362             ContextMenu subMenu;
363             populateContextMenu(WTFMove(*item.subItems), subMenu);
364
365             menu.appendItem({ SubmenuType, ContextMenuItemTagNoAction, item.label, &subMenu });
366             continue;
367         }
368
369         auto type = item.type == "checkbox" ? CheckableActionType : ActionType;
370         auto action = static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + item.id.value_or(0));
371         ContextMenuItem menuItem = { type, action, item.label };
372         if (item.enabled)
373             menuItem.setEnabled(*item.enabled);
374         if (item.checked)
375             menuItem.setChecked(*item.checked);
376         menu.appendItem(menuItem);
377     }
378 }
379 #endif
380
381 void InspectorFrontendHost::showContextMenu(Event& event, Vector<ContextMenuItem>&& items)
382 {
383 #if ENABLE(CONTEXT_MENUS)
384     ASSERT(m_frontendPage);
385
386     auto& state = *execStateFromPage(debuggerWorld(), m_frontendPage);
387     auto value = state.lexicalGlobalObject()->get(&state, JSC::Identifier::fromString(&state.vm(), "InspectorFrontendAPI"));
388     ASSERT(value);
389     ASSERT(value.isObject());
390     auto* frontendAPIObject = asObject(value);
391     
392     ContextMenu menu;
393     populateContextMenu(WTFMove(items), menu);
394
395     auto menuProvider = FrontendMenuProvider::create(this, { &state, frontendAPIObject }, menu.items());
396     m_menuProvider = menuProvider.ptr();
397     m_frontendPage->contextMenuController().showContextMenu(event, menuProvider);
398 #else
399     UNUSED_PARAM(event);
400     UNUSED_PARAM(items);
401 #endif
402 }
403
404 void InspectorFrontendHost::dispatchEventAsContextMenuEvent(Event& event)
405 {
406 #if ENABLE(CONTEXT_MENUS) && USE(ACCESSIBILITY_CONTEXT_MENUS)
407     if (!is<MouseEvent>(event))
408         return;
409
410     auto& mouseEvent = downcast<MouseEvent>(event);
411     IntPoint mousePoint { mouseEvent.clientX(), mouseEvent.clientY() };
412     auto& frame = *downcast<Node>(mouseEvent.target())->document().frame();
413
414     m_frontendPage->contextMenuController().showContextMenuAt(frame, mousePoint);
415 #else
416     UNUSED_PARAM(event);
417 #endif
418 }
419
420 bool InspectorFrontendHost::isUnderTest()
421 {
422     return m_client && m_client->isUnderTest();
423 }
424
425 void InspectorFrontendHost::unbufferedLog(const String& message)
426 {
427     // This is used only for debugging inspector tests.
428     WTFLogAlways("%s", message.utf8().data());
429 }
430
431 void InspectorFrontendHost::beep()
432 {
433     PAL::systemBeep();
434 }
435
436 void InspectorFrontendHost::inspectInspector()
437 {
438     if (m_frontendPage)
439         m_frontendPage->inspectorController().show();
440 }
441
442 bool InspectorFrontendHost::supportsShowCertificate() const
443 {
444 #if PLATFORM(COCOA)
445     return true;
446 #else
447     return false;
448 #endif
449 }
450
451 bool InspectorFrontendHost::showCertificate(const String& serializedCertificate)
452 {
453     if (!m_client)
454         return false;
455
456     Vector<uint8_t> data;
457     if (!base64Decode(serializedCertificate, data))
458         return false;
459
460     CertificateInfo certificateInfo;
461     WTF::Persistence::Decoder decoder(data.data(), data.size());
462     if (!decoder.decode(certificateInfo))
463         return false;
464
465     if (certificateInfo.isEmpty())
466         return false;
467
468     m_client->showCertificate(certificateInfo);
469     return true;
470 }
471
472 } // namespace WebCore