1/*
2 * Copyright (C) 2007-2017 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <[email protected]>
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
63namespace WebCore {
64
65using namespace Inspector;
66
67#if ENABLE(CONTEXT_MENUS)
68class FrontendMenuProvider : public ContextMenuProvider {
69public:
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
81private:
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
129InspectorFrontendHost::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
138InspectorFrontendHost::~InspectorFrontendHost()
139{
140 ASSERT(!m_client);
141}
142
143void 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
153void 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
166void InspectorFrontendHost::loaded()
167{
168 if (m_client)
169 m_client->frontendLoaded();
170}
171
172void 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
186void InspectorFrontendHost::closeWindow()
187{
188 if (m_client) {
189 m_client->closeWindow();
190 disconnectClient(); // Disconnect from client.
191 }
192}
193
194void InspectorFrontendHost::reopen()
195{
196 if (m_client)
197 m_client->reopen();
198}
199
200void InspectorFrontendHost::bringToFront()
201{
202 if (m_client)
203 m_client->bringToFront();
204}
205
206void InspectorFrontendHost::inspectedURLChanged(const String& newURL)
207{
208 if (m_client)
209 m_client->inspectedURLChanged(newURL);
210}
211
212void InspectorFrontendHost::setZoomFactor(float zoom)
213{
214 if (m_frontendPage)
215 m_frontendPage->mainFrame().setPageAndTextZoomFactors(zoom, 1);
216}
217
218float InspectorFrontendHost::zoomFactor()
219{
220 if (m_frontendPage)
221 return m_frontendPage->mainFrame().pageZoomFactor();
222
223 return 1.0;
224}
225
226String InspectorFrontendHost::userInterfaceLayoutDirection()
227{
228 if (m_client && m_client->userInterfaceLayoutDirection() == UserInterfaceLayoutDirection::RTL)
229 return "rtl"_s;
230
231 return "ltr"_s;
232}
233
234void InspectorFrontendHost::setAttachedWindowHeight(unsigned height)
235{
236 if (m_client)
237 m_client->changeAttachedWindowHeight(height);
238}
239
240void InspectorFrontendHost::setAttachedWindowWidth(unsigned width)
241{
242 if (m_client)
243 m_client->changeAttachedWindowWidth(width);
244}
245
246void 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
252void InspectorFrontendHost::startWindowDrag()
253{
254 if (m_client)
255 m_client->startWindowDrag();
256}
257
258void InspectorFrontendHost::moveWindowBy(float x, float y) const
259{
260 if (m_client)
261 m_client->moveWindowBy(x, y);
262}
263
264bool InspectorFrontendHost::isRemote() const
265{
266 return m_client ? m_client->isRemote() : false;
267}
268
269String InspectorFrontendHost::localizedStringsURL()
270{
271 return m_client ? m_client->localizedStringsURL() : String();
272}
273
274String InspectorFrontendHost::backendCommandsURL()
275{
276 return m_client ? m_client->backendCommandsURL() : String();
277}
278
279String InspectorFrontendHost::debuggableType()
280{
281 return m_client ? m_client->debuggableType() : String();
282}
283
284unsigned InspectorFrontendHost::inspectionLevel()
285{
286 return m_client ? m_client->inspectionLevel() : 1;
287}
288
289String 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
306String InspectorFrontendHost::port()
307{
308#if PLATFORM(GTK)
309 return "gtk"_s;
310#else
311 return "unknown"_s;
312#endif
313}
314
315void InspectorFrontendHost::copyText(const String& text)
316{
317 Pasteboard::createForCopyAndPaste()->writePlainText(text, Pasteboard::CannotSmartReplace);
318}
319
320void 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
331void 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
340bool InspectorFrontendHost::canSave()
341{
342 if (m_client)
343 return m_client->canSave();
344 return false;
345}
346
347void 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
353void InspectorFrontendHost::append(const String& url, const String& content)
354{
355 if (m_client)
356 m_client->append(url, content);
357}
358
359void InspectorFrontendHost::close(const String&)
360{
361}
362
363void InspectorFrontendHost::sendMessageToBackend(const String& message)
364{
365 if (m_client)
366 m_client->sendMessageToBackend(message);
367}
368
369#if ENABLE(CONTEXT_MENUS)
370
371static 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
399void 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
422void 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 auto& frame = *downcast<Node>(mouseEvent.target())->document().frame();
430 m_frontendPage->contextMenuController().showContextMenuAt(frame, roundedIntPoint(mouseEvent.absoluteLocation()));
431#else
432 UNUSED_PARAM(event);
433#endif
434}
435
436bool InspectorFrontendHost::isUnderTest()
437{
438 return m_client && m_client->isUnderTest();
439}
440
441void InspectorFrontendHost::unbufferedLog(const String& message)
442{
443 // This is used only for debugging inspector tests.
444 WTFLogAlways("%s", message.utf8().data());
445}
446
447void InspectorFrontendHost::beep()
448{
449 PAL::systemBeep();
450}
451
452void InspectorFrontendHost::inspectInspector()
453{
454 if (m_frontendPage)
455 m_frontendPage->inspectorController().show();
456}
457
458bool InspectorFrontendHost::isBeingInspected()
459{
460 if (!m_frontendPage)
461 return false;
462
463 InspectorController& inspectorController = m_frontendPage->inspectorController();
464 return inspectorController.hasLocalFrontend() || inspectorController.hasRemoteFrontend();
465}
466
467bool InspectorFrontendHost::supportsShowCertificate() const
468{
469#if PLATFORM(COCOA)
470 return true;
471#else
472 return false;
473#endif
474}
475
476bool InspectorFrontendHost::showCertificate(const String& serializedCertificate)
477{
478 if (!m_client)
479 return false;
480
481 Vector<uint8_t> data;
482 if (!base64Decode(serializedCertificate, data))
483 return false;
484
485 CertificateInfo certificateInfo;
486 WTF::Persistence::Decoder decoder(data.data(), data.size());
487 if (!decoder.decode(certificateInfo))
488 return false;
489
490 if (certificateInfo.isEmpty())
491 return false;
492
493 m_client->showCertificate(certificateInfo);
494 return true;
495}
496
497} // namespace WebCore
498