64d1abaec278b1fe142e21612b16599b8949cb20
[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 "FloatRect.h"
43 #include "FocusController.h"
44 #include "Frame.h"
45 #include "HitTestResult.h"
46 #include "InspectorController.h"
47 #include "InspectorFrontendClient.h"
48 #include "JSDOMConvertInterface.h"
49 #include "JSDOMExceptionHandling.h"
50 #include "JSExecState.h"
51 #include "JSInspectorFrontendHost.h"
52 #include "MouseEvent.h"
53 #include "Node.h"
54 #include "Page.h"
55 #include "Pasteboard.h"
56 #include "ScriptState.h"
57 #include "UserGestureIndicator.h"
58 #include <JavaScriptCore/ScriptFunctionCall.h>
59 #include <pal/system/Sound.h>
60 #include <wtf/StdLibExtras.h>
61 #include <wtf/text/Base64.h>
62
63 namespace WebCore {
64
65 using namespace Inspector;
66
67 #if ENABLE(CONTEXT_MENUS)
68 class FrontendMenuProvider : public ContextMenuProvider {
69 public:
70     static Ref<FrontendMenuProvider> create(InspectorFrontendHost* frontendHost, Deprecated::ScriptObject frontendApiObject, const Vector<ContextMenuItem>& items)
71     {
72         return adoptRef(*new FrontendMenuProvider(frontendHost, frontendApiObject, items));
73     }
74     
75     void disconnect()
76     {
77         m_frontendApiObject = { };
78         m_frontendHost = nullptr;
79     }
80     
81 private:
82     FrontendMenuProvider(InspectorFrontendHost* frontendHost, Deprecated::ScriptObject frontendApiObject, const Vector<ContextMenuItem>& items)
83         : m_frontendHost(frontendHost)
84         , m_frontendApiObject(frontendApiObject)
85         , m_items(items)
86     {
87     }
88
89     virtual ~FrontendMenuProvider()
90     {
91         contextMenuCleared();
92     }
93     
94     void populateContextMenu(ContextMenu* menu) override
95     {
96         for (auto& item : m_items)
97             menu->appendItem(item);
98     }
99     
100     void contextMenuItemSelected(ContextMenuAction action, const String&) override
101     {
102         if (m_frontendHost) {
103             UserGestureIndicator gestureIndicator(ProcessingUserGesture);
104             int itemNumber = action - ContextMenuItemBaseCustomTag;
105
106             Deprecated::ScriptFunctionCall function(m_frontendApiObject, "contextMenuItemSelected", WebCore::functionCallHandlerFromAnyThread);
107             function.appendArgument(itemNumber);
108             function.call();
109         }
110     }
111     
112     void contextMenuCleared() override
113     {
114         if (m_frontendHost) {
115             Deprecated::ScriptFunctionCall function(m_frontendApiObject, "contextMenuCleared", WebCore::functionCallHandlerFromAnyThread);
116             function.call();
117
118             m_frontendHost->m_menuProvider = nullptr;
119         }
120         m_items.clear();
121     }
122
123     InspectorFrontendHost* m_frontendHost;
124     Deprecated::ScriptObject m_frontendApiObject;
125     Vector<ContextMenuItem> m_items;
126 };
127 #endif
128
129 InspectorFrontendHost::InspectorFrontendHost(InspectorFrontendClient* client, Page* frontendPage)
130     : m_client(client)
131     , m_frontendPage(frontendPage)
132 #if ENABLE(CONTEXT_MENUS)
133     , m_menuProvider(nullptr)
134 #endif
135 {
136 }
137
138 InspectorFrontendHost::~InspectorFrontendHost()
139 {
140     ASSERT(!m_client);
141 }
142
143 void InspectorFrontendHost::disconnectClient()
144 {
145     m_client = nullptr;
146 #if ENABLE(CONTEXT_MENUS)
147     if (m_menuProvider)
148         m_menuProvider->disconnect();
149 #endif
150     m_frontendPage = nullptr;
151 }
152
153 void InspectorFrontendHost::addSelfToGlobalObjectInWorld(DOMWrapperWorld& world)
154 {
155     auto& state = *execStateFromPage(world, m_frontendPage);
156     auto& vm = state.vm();
157     JSC::JSLockHolder lock(vm);
158     auto scope = DECLARE_CATCH_SCOPE(vm);
159
160     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject());
161     globalObject.putDirect(vm, JSC::Identifier::fromString(&vm, "InspectorFrontendHost"), toJS<IDLInterface<InspectorFrontendHost>>(state, globalObject, *this));
162     if (UNLIKELY(scope.exception()))
163         reportException(&state, scope.exception());
164 }
165
166 void InspectorFrontendHost::loaded()
167 {
168     if (m_client)
169         m_client->frontendLoaded();
170 }
171
172 void InspectorFrontendHost::requestSetDockSide(const String& side)
173 {
174     if (!m_client)
175         return;
176     if (side == "undocked")
177         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Undocked);
178     else if (side == "right")
179         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Right);
180     else if (side == "left")
181         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Left);
182     else if (side == "bottom")
183         m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Bottom);
184 }
185
186 void InspectorFrontendHost::closeWindow()
187 {
188     if (m_client) {
189         m_client->closeWindow();
190         disconnectClient(); // Disconnect from client.
191     }
192 }
193
194 void InspectorFrontendHost::reopen()
195 {
196     if (m_client)
197         m_client->reopen();
198 }
199
200 void InspectorFrontendHost::bringToFront()
201 {
202     if (m_client)
203         m_client->bringToFront();
204 }
205
206 void InspectorFrontendHost::inspectedURLChanged(const String& newURL)
207 {
208     if (m_client)
209         m_client->inspectedURLChanged(newURL);
210 }
211
212 void InspectorFrontendHost::setZoomFactor(float zoom)
213 {
214     if (m_frontendPage)
215         m_frontendPage->mainFrame().setPageAndTextZoomFactors(zoom, 1);
216 }
217
218 float InspectorFrontendHost::zoomFactor()
219 {
220     if (m_frontendPage)
221         return m_frontendPage->mainFrame().pageZoomFactor();
222
223     return 1.0;
224 }
225
226 String InspectorFrontendHost::userInterfaceLayoutDirection()
227 {
228     if (m_client && m_client->userInterfaceLayoutDirection() == UserInterfaceLayoutDirection::RTL)
229         return "rtl"_s;
230
231     return "ltr"_s;
232 }
233
234 void InspectorFrontendHost::setAttachedWindowHeight(unsigned height)
235 {
236     if (m_client)
237         m_client->changeAttachedWindowHeight(height);
238 }
239
240 void InspectorFrontendHost::setAttachedWindowWidth(unsigned width)
241 {
242     if (m_client)
243         m_client->changeAttachedWindowWidth(width);
244 }
245
246 void InspectorFrontendHost::setSheetRect(float x, float y, unsigned width, unsigned height)
247 {
248     if (m_client)
249         m_client->changeSheetRect(FloatRect(x, y, width, height));
250 }
251
252 void InspectorFrontendHost::startWindowDrag()
253 {
254     if (m_client)
255         m_client->startWindowDrag();
256 }
257
258 void InspectorFrontendHost::moveWindowBy(float x, float y) const
259 {
260     if (m_client)
261         m_client->moveWindowBy(x, y);
262 }
263
264 bool InspectorFrontendHost::isRemote() const
265 {
266     return m_client ? m_client->isRemote() : false;
267 }
268
269 String InspectorFrontendHost::localizedStringsURL()
270 {
271     return m_client ? m_client->localizedStringsURL() : String();
272 }
273
274 String InspectorFrontendHost::backendCommandsURL()
275 {
276     return m_client ? m_client->backendCommandsURL() : String();
277 }
278
279 String InspectorFrontendHost::debuggableType()
280 {
281     return m_client ? m_client->debuggableType() : String();
282 }
283
284 unsigned InspectorFrontendHost::inspectionLevel()
285 {
286     return m_client ? m_client->inspectionLevel() : 1;
287 }
288
289 String InspectorFrontendHost::platform()
290 {
291 #if PLATFORM(MAC) || PLATFORM(IOS_FAMILY)
292     return "mac"_s;
293 #elif OS(WINDOWS)
294     return "windows"_s;
295 #elif OS(LINUX)
296     return "linux"_s;
297 #elif OS(FREEBSD)
298     return "freebsd"_s;
299 #elif OS(OPENBSD)
300     return "openbsd"_s;
301 #else
302     return "unknown"_s;
303 #endif
304 }
305
306 String InspectorFrontendHost::port()
307 {
308 #if PLATFORM(GTK)
309     return "gtk"_s;
310 #else
311     return "unknown"_s;
312 #endif
313 }
314
315 void InspectorFrontendHost::copyText(const String& text)
316 {
317     Pasteboard::createForCopyAndPaste()->writePlainText(text, Pasteboard::CannotSmartReplace);
318 }
319
320 void InspectorFrontendHost::killText(const String& text, bool shouldPrependToKillRing, bool shouldStartNewSequence)
321 {
322     if (!m_frontendPage)
323         return;
324
325     Editor& editor = m_frontendPage->focusController().focusedOrMainFrame().editor();
326     editor.setStartNewKillRingSequence(shouldStartNewSequence);
327     Editor::KillRingInsertionMode insertionMode = shouldPrependToKillRing ? Editor::KillRingInsertionMode::PrependText : Editor::KillRingInsertionMode::AppendText;
328     editor.addTextToKillRing(text, insertionMode);
329 }
330
331 void InspectorFrontendHost::openInNewTab(const String& url)
332 {
333     if (WTF::protocolIsJavaScript(url))
334         return;
335
336     if (m_client)
337         m_client->openInNewTab(url);
338 }
339
340 bool InspectorFrontendHost::canSave()
341 {
342     if (m_client)
343         return m_client->canSave();
344     return false;
345 }
346
347 void InspectorFrontendHost::save(const String& url, const String& content, bool base64Encoded, bool forceSaveAs)
348 {
349     if (m_client)
350         m_client->save(url, content, base64Encoded, forceSaveAs);
351 }
352
353 void InspectorFrontendHost::append(const String& url, const String& content)
354 {
355     if (m_client)
356         m_client->append(url, content);
357 }
358
359 void InspectorFrontendHost::close(const String&)
360 {
361 }
362
363 void InspectorFrontendHost::sendMessageToBackend(const String& message)
364 {
365     if (m_client)
366         m_client->sendMessageToBackend(message);
367 }
368
369 #if ENABLE(CONTEXT_MENUS)
370
371 static void populateContextMenu(Vector<InspectorFrontendHost::ContextMenuItem>&& items, ContextMenu& menu)
372 {
373     for (auto& item : items) {
374         if (item.type == "separator") {
375             menu.appendItem({ SeparatorType, ContextMenuItemTagNoAction, { } });
376             continue;
377         }
378
379         if (item.type == "subMenu" && item.subItems) {
380             ContextMenu subMenu;
381             populateContextMenu(WTFMove(*item.subItems), subMenu);
382
383             menu.appendItem({ SubmenuType, ContextMenuItemTagNoAction, item.label, &subMenu });
384             continue;
385         }
386
387         auto type = item.type == "checkbox" ? CheckableActionType : ActionType;
388         auto action = static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + item.id.valueOr(0));
389         ContextMenuItem menuItem = { type, action, item.label };
390         if (item.enabled)
391             menuItem.setEnabled(*item.enabled);
392         if (item.checked)
393             menuItem.setChecked(*item.checked);
394         menu.appendItem(menuItem);
395     }
396 }
397 #endif
398
399 void InspectorFrontendHost::showContextMenu(Event& event, Vector<ContextMenuItem>&& items)
400 {
401 #if ENABLE(CONTEXT_MENUS)
402     ASSERT(m_frontendPage);
403
404     auto& state = *execStateFromPage(debuggerWorld(), m_frontendPage);
405     auto value = state.lexicalGlobalObject()->get(&state, JSC::Identifier::fromString(&state.vm(), "InspectorFrontendAPI"));
406     ASSERT(value);
407     ASSERT(value.isObject());
408     auto* frontendAPIObject = asObject(value);
409     
410     ContextMenu menu;
411     populateContextMenu(WTFMove(items), menu);
412
413     auto menuProvider = FrontendMenuProvider::create(this, { &state, frontendAPIObject }, menu.items());
414     m_menuProvider = menuProvider.ptr();
415     m_frontendPage->contextMenuController().showContextMenu(event, menuProvider);
416 #else
417     UNUSED_PARAM(event);
418     UNUSED_PARAM(items);
419 #endif
420 }
421
422 void InspectorFrontendHost::dispatchEventAsContextMenuEvent(Event& event)
423 {
424 #if ENABLE(CONTEXT_MENUS) && USE(ACCESSIBILITY_CONTEXT_MENUS)
425     if (!is<MouseEvent>(event))
426         return;
427
428     auto& mouseEvent = downcast<MouseEvent>(event);
429     IntPoint mousePoint { mouseEvent.clientX(), mouseEvent.clientY() };
430     auto& frame = *downcast<Node>(mouseEvent.target())->document().frame();
431
432     m_frontendPage->contextMenuController().showContextMenuAt(frame, mousePoint);
433 #else
434     UNUSED_PARAM(event);
435 #endif
436 }
437
438 bool InspectorFrontendHost::isUnderTest()
439 {
440     return m_client && m_client->isUnderTest();
441 }
442
443 void InspectorFrontendHost::unbufferedLog(const String& message)
444 {
445     // This is used only for debugging inspector tests.
446     WTFLogAlways("%s", message.utf8().data());
447 }
448
449 void InspectorFrontendHost::beep()
450 {
451     PAL::systemBeep();
452 }
453
454 void InspectorFrontendHost::inspectInspector()
455 {
456     if (m_frontendPage)
457         m_frontendPage->inspectorController().show();
458 }
459
460 bool InspectorFrontendHost::isBeingInspected()
461 {
462     if (!m_frontendPage)
463         return false;
464
465     InspectorController& inspectorController = m_frontendPage->inspectorController();
466     return inspectorController.hasLocalFrontend() || inspectorController.hasRemoteFrontend();
467 }
468
469 bool InspectorFrontendHost::supportsShowCertificate() const
470 {
471 #if PLATFORM(COCOA)
472     return true;
473 #else
474     return false;
475 #endif
476 }
477
478 bool InspectorFrontendHost::showCertificate(const String& serializedCertificate)
479 {
480     if (!m_client)
481         return false;
482
483     Vector<uint8_t> data;
484     if (!base64Decode(serializedCertificate, data))
485         return false;
486
487     CertificateInfo certificateInfo;
488     WTF::Persistence::Decoder decoder(data.data(), data.size());
489     if (!decoder.decode(certificateInfo))
490         return false;
491
492     if (certificateInfo.isEmpty())
493         return false;
494
495     m_client->showCertificate(certificateInfo);
496     return true;
497 }
498
499 } // namespace WebCore