1/*
2 * Copyright (C) 2006, 2007, 2009, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies)
4 * Copyright (C) 2012, Samsung Electronics. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "Chrome.h"
24
25#include "ChromeClient.h"
26#include "DNS.h"
27#include "DateTimeChooser.h"
28#include "Document.h"
29#include "DocumentType.h"
30#include "FileIconLoader.h"
31#include "FileChooser.h"
32#include "FileList.h"
33#include "FloatRect.h"
34#include "FrameTree.h"
35#include "Geolocation.h"
36#include "HTMLFormElement.h"
37#include "HTMLInputElement.h"
38#include "HTMLNames.h"
39#include "HitTestResult.h"
40#include "Icon.h"
41#include "InspectorInstrumentation.h"
42#include "MainFrame.h"
43#include "Page.h"
44#include "PageGroupLoadDeferrer.h"
45#include "PopupOpeningObserver.h"
46#include "RenderObject.h"
47#include "ResourceHandle.h"
48#include "SecurityOrigin.h"
49#include "Settings.h"
50#include "StorageNamespace.h"
51#include "WindowFeatures.h"
52#include <wtf/PassRefPtr.h>
53#include <wtf/RefPtr.h>
54#include <wtf/Vector.h>
55#include <wtf/text/StringBuilder.h>
56
57#if ENABLE(INPUT_TYPE_COLOR)
58#include "ColorChooser.h"
59#endif
60
61namespace WebCore {
62
63using namespace HTMLNames;
64
65Chrome::Chrome(Page& page, ChromeClient& client)
66    : m_page(page)
67    , m_client(client)
68    , m_displayID(0)
69#if PLATFORM(IOS)
70    , m_isDispatchViewportDataDidChangeSuppressed(false)
71#endif
72{
73}
74
75Chrome::~Chrome()
76{
77    m_client.chromeDestroyed();
78}
79
80void Chrome::invalidateRootView(const IntRect& updateRect)
81{
82    m_client.invalidateRootView(updateRect);
83}
84
85void Chrome::invalidateContentsAndRootView(const IntRect& updateRect)
86{
87    m_client.invalidateContentsAndRootView(updateRect);
88}
89
90void Chrome::invalidateContentsForSlowScroll(const IntRect& updateRect)
91{
92    m_client.invalidateContentsForSlowScroll(updateRect);
93}
94
95void Chrome::scroll(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect)
96{
97    m_client.scroll(scrollDelta, rectToScroll, clipRect);
98    InspectorInstrumentation::didScroll(&m_page);
99}
100
101#if USE(TILED_BACKING_STORE)
102void Chrome::delegatedScrollRequested(const IntPoint& scrollPoint)
103{
104    m_client.delegatedScrollRequested(scrollPoint);
105}
106#endif
107
108IntPoint Chrome::screenToRootView(const IntPoint& point) const
109{
110    return m_client.screenToRootView(point);
111}
112
113IntRect Chrome::rootViewToScreen(const IntRect& rect) const
114{
115    return m_client.rootViewToScreen(rect);
116}
117
118#if PLATFORM(IOS)
119IntPoint Chrome::accessibilityScreenToRootView(const IntPoint& point) const
120{
121    return m_client.accessibilityScreenToRootView(point);
122}
123
124IntRect Chrome::rootViewToAccessibilityScreen(const IntRect& rect) const
125{
126    return m_client.rootViewToAccessibilityScreen(rect);
127}
128#endif
129
130PlatformPageClient Chrome::platformPageClient() const
131{
132    return m_client.platformPageClient();
133}
134
135void Chrome::contentsSizeChanged(Frame* frame, const IntSize& size) const
136{
137    m_client.contentsSizeChanged(frame, size);
138}
139
140void Chrome::scrollRectIntoView(const IntRect& rect) const
141{
142    m_client.scrollRectIntoView(rect);
143}
144
145void Chrome::scrollbarsModeDidChange() const
146{
147    m_client.scrollbarsModeDidChange();
148}
149
150void Chrome::setWindowRect(const FloatRect& rect) const
151{
152    m_client.setWindowRect(rect);
153}
154
155FloatRect Chrome::windowRect() const
156{
157    return m_client.windowRect();
158}
159
160FloatRect Chrome::pageRect() const
161{
162    return m_client.pageRect();
163}
164
165void Chrome::focus() const
166{
167    m_client.focus();
168}
169
170void Chrome::unfocus() const
171{
172    m_client.unfocus();
173}
174
175bool Chrome::canTakeFocus(FocusDirection direction) const
176{
177    return m_client.canTakeFocus(direction);
178}
179
180void Chrome::takeFocus(FocusDirection direction) const
181{
182    m_client.takeFocus(direction);
183}
184
185void Chrome::focusedElementChanged(Element* element) const
186{
187    m_client.focusedElementChanged(element);
188}
189
190void Chrome::focusedFrameChanged(Frame* frame) const
191{
192    m_client.focusedFrameChanged(frame);
193}
194
195Page* Chrome::createWindow(Frame* frame, const FrameLoadRequest& request, const WindowFeatures& features, const NavigationAction& action) const
196{
197    Page* newPage = m_client.createWindow(frame, request, features, action);
198    if (!newPage)
199        return 0;
200
201    if (StorageNamespace* oldSessionStorage = m_page.sessionStorage(false))
202        newPage->setSessionStorage(oldSessionStorage->copy(newPage));
203
204    return newPage;
205}
206
207void Chrome::show() const
208{
209    m_client.show();
210}
211
212bool Chrome::canRunModal() const
213{
214    return m_client.canRunModal();
215}
216
217static bool canRunModalIfDuringPageDismissal(Page& page, ChromeClient::DialogType dialog, const String& message)
218{
219    for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
220        FrameLoader::PageDismissalType dismissal = frame->loader().pageDismissalEventBeingDispatched();
221        if (dismissal != FrameLoader::NoDismissal)
222            return page.chrome().client().shouldRunModalDialogDuringPageDismissal(dialog, message, dismissal);
223    }
224    return true;
225}
226
227bool Chrome::canRunModalNow() const
228{
229    // If loads are blocked, we can't run modal because the contents
230    // of the modal dialog will never show up!
231    return canRunModal() && !ResourceHandle::loadsBlocked()
232           && canRunModalIfDuringPageDismissal(m_page, ChromeClient::HTMLDialog, String());
233}
234
235void Chrome::runModal() const
236{
237    // Defer callbacks in all the other pages in this group, so we don't try to run JavaScript
238    // in a way that could interact with this view.
239    PageGroupLoadDeferrer deferrer(m_page, false);
240
241    TimerBase::fireTimersInNestedEventLoop();
242    m_client.runModal();
243}
244
245void Chrome::setToolbarsVisible(bool b) const
246{
247    m_client.setToolbarsVisible(b);
248}
249
250bool Chrome::toolbarsVisible() const
251{
252    return m_client.toolbarsVisible();
253}
254
255void Chrome::setStatusbarVisible(bool b) const
256{
257    m_client.setStatusbarVisible(b);
258}
259
260bool Chrome::statusbarVisible() const
261{
262    return m_client.statusbarVisible();
263}
264
265void Chrome::setScrollbarsVisible(bool b) const
266{
267    m_client.setScrollbarsVisible(b);
268}
269
270bool Chrome::scrollbarsVisible() const
271{
272    return m_client.scrollbarsVisible();
273}
274
275void Chrome::setMenubarVisible(bool b) const
276{
277    m_client.setMenubarVisible(b);
278}
279
280bool Chrome::menubarVisible() const
281{
282    return m_client.menubarVisible();
283}
284
285void Chrome::setResizable(bool b) const
286{
287    m_client.setResizable(b);
288}
289
290bool Chrome::canRunBeforeUnloadConfirmPanel()
291{
292    return m_client.canRunBeforeUnloadConfirmPanel();
293}
294
295bool Chrome::runBeforeUnloadConfirmPanel(const String& message, Frame* frame)
296{
297    // Defer loads in case the client method runs a new event loop that would
298    // otherwise cause the load to continue while we're in the middle of executing JavaScript.
299    PageGroupLoadDeferrer deferrer(m_page, true);
300
301    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRunJavaScriptDialog(&m_page, message);
302    bool ok = m_client.runBeforeUnloadConfirmPanel(message, frame);
303    InspectorInstrumentation::didRunJavaScriptDialog(cookie);
304    return ok;
305}
306
307void Chrome::closeWindowSoon()
308{
309    m_client.closeWindowSoon();
310}
311
312void Chrome::runJavaScriptAlert(Frame* frame, const String& message)
313{
314    if (!canRunModalIfDuringPageDismissal(m_page, ChromeClient::AlertDialog, message))
315        return;
316
317    // Defer loads in case the client method runs a new event loop that would
318    // otherwise cause the load to continue while we're in the middle of executing JavaScript.
319    PageGroupLoadDeferrer deferrer(m_page, true);
320
321    ASSERT(frame);
322    notifyPopupOpeningObservers();
323    String displayMessage = frame->displayStringModifiedByEncoding(message);
324
325    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRunJavaScriptDialog(&m_page, displayMessage);
326    m_client.runJavaScriptAlert(frame, displayMessage);
327    InspectorInstrumentation::didRunJavaScriptDialog(cookie);
328}
329
330bool Chrome::runJavaScriptConfirm(Frame* frame, const String& message)
331{
332    if (!canRunModalIfDuringPageDismissal(m_page, ChromeClient::ConfirmDialog, message))
333        return false;
334
335    // Defer loads in case the client method runs a new event loop that would
336    // otherwise cause the load to continue while we're in the middle of executing JavaScript.
337    PageGroupLoadDeferrer deferrer(m_page, true);
338
339    ASSERT(frame);
340    notifyPopupOpeningObservers();
341    String displayMessage = frame->displayStringModifiedByEncoding(message);
342
343    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRunJavaScriptDialog(&m_page, displayMessage);
344    bool ok = m_client.runJavaScriptConfirm(frame, displayMessage);
345    InspectorInstrumentation::didRunJavaScriptDialog(cookie);
346    return ok;
347}
348
349bool Chrome::runJavaScriptPrompt(Frame* frame, const String& prompt, const String& defaultValue, String& result)
350{
351    if (!canRunModalIfDuringPageDismissal(m_page, ChromeClient::PromptDialog, prompt))
352        return false;
353
354    // Defer loads in case the client method runs a new event loop that would
355    // otherwise cause the load to continue while we're in the middle of executing JavaScript.
356    PageGroupLoadDeferrer deferrer(m_page, true);
357
358    ASSERT(frame);
359    notifyPopupOpeningObservers();
360    String displayPrompt = frame->displayStringModifiedByEncoding(prompt);
361
362    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRunJavaScriptDialog(&m_page, displayPrompt);
363    bool ok = m_client.runJavaScriptPrompt(frame, displayPrompt, frame->displayStringModifiedByEncoding(defaultValue), result);
364    InspectorInstrumentation::didRunJavaScriptDialog(cookie);
365
366    if (ok)
367        result = frame->displayStringModifiedByEncoding(result);
368
369    return ok;
370}
371
372void Chrome::setStatusbarText(Frame* frame, const String& status)
373{
374    ASSERT(frame);
375    m_client.setStatusbarText(frame->displayStringModifiedByEncoding(status));
376}
377
378bool Chrome::shouldInterruptJavaScript()
379{
380    // Defer loads in case the client method runs a new event loop that would
381    // otherwise cause the load to continue while we're in the middle of executing JavaScript.
382    PageGroupLoadDeferrer deferrer(m_page, true);
383
384    return m_client.shouldInterruptJavaScript();
385}
386
387IntRect Chrome::windowResizerRect() const
388{
389    return m_client.windowResizerRect();
390}
391
392void Chrome::mouseDidMoveOverElement(const HitTestResult& result, unsigned modifierFlags)
393{
394    if (result.innerNode() && result.innerNode()->document().isDNSPrefetchEnabled())
395        prefetchDNS(result.absoluteLinkURL().host());
396    m_client.mouseDidMoveOverElement(result, modifierFlags);
397
398    InspectorInstrumentation::mouseDidMoveOverElement(&m_page, result, modifierFlags);
399}
400
401void Chrome::setToolTip(const HitTestResult& result)
402{
403    // First priority is a potential toolTip representing a spelling or grammar error
404    TextDirection toolTipDirection;
405    String toolTip = result.spellingToolTip(toolTipDirection);
406
407    // Next priority is a toolTip from a URL beneath the mouse (if preference is set to show those).
408    if (toolTip.isEmpty() && m_page.settings().showsURLsInToolTips()) {
409        if (Element* element = result.innerNonSharedElement()) {
410            // Get tooltip representing form action, if relevant
411            if (isHTMLInputElement(element)) {
412                HTMLInputElement* input = toHTMLInputElement(element);
413                if (input->isSubmitButton()) {
414                    if (HTMLFormElement* form = input->form()) {
415                        toolTip = form->action();
416                        if (form->renderer())
417                            toolTipDirection = form->renderer()->style().direction();
418                        else
419                            toolTipDirection = LTR;
420                    }
421                }
422            }
423        }
424
425        // Get tooltip representing link's URL
426        if (toolTip.isEmpty()) {
427            // FIXME: Need to pass this URL through userVisibleString once that's in WebCore
428            toolTip = result.absoluteLinkURL().string();
429            // URL always display as LTR.
430            toolTipDirection = LTR;
431        }
432    }
433
434    // Next we'll consider a tooltip for element with "title" attribute
435    if (toolTip.isEmpty())
436        toolTip = result.title(toolTipDirection);
437
438    if (toolTip.isEmpty() && m_page.settings().showsToolTipOverTruncatedText())
439        toolTip = result.innerTextIfTruncated(toolTipDirection);
440
441    // Lastly, for <input type="file"> that allow multiple files, we'll consider a tooltip for the selected filenames
442    if (toolTip.isEmpty()) {
443        if (Element* element = result.innerNonSharedElement()) {
444            if (isHTMLInputElement(element)) {
445                toolTip = toHTMLInputElement(element)->defaultToolTip();
446
447                // FIXME: We should obtain text direction of tooltip from
448                // ChromeClient or platform. As of October 2011, all client
449                // implementations don't use text direction information for
450                // ChromeClient::setToolTip. We'll work on tooltip text
451                // direction during bidi cleanup in form inputs.
452                toolTipDirection = LTR;
453            }
454        }
455    }
456
457    m_client.setToolTip(toolTip, toolTipDirection);
458}
459
460void Chrome::print(Frame* frame)
461{
462    // FIXME: This should have PageGroupLoadDeferrer, like runModal() or runJavaScriptAlert(), becasue it's no different from those.
463    m_client.print(frame);
464}
465
466void Chrome::enableSuddenTermination()
467{
468    m_client.enableSuddenTermination();
469}
470
471void Chrome::disableSuddenTermination()
472{
473    m_client.disableSuddenTermination();
474}
475
476#if ENABLE(INPUT_TYPE_COLOR)
477PassOwnPtr<ColorChooser> Chrome::createColorChooser(ColorChooserClient* client, const Color& initialColor)
478{
479    notifyPopupOpeningObservers();
480    return m_client.createColorChooser(client, initialColor);
481}
482#endif
483
484#if ENABLE(DATE_AND_TIME_INPUT_TYPES) && !PLATFORM(IOS)
485PassRefPtr<DateTimeChooser> Chrome::openDateTimeChooser(DateTimeChooserClient* client, const DateTimeChooserParameters& parameters)
486{
487    notifyPopupOpeningObservers();
488    return m_client.openDateTimeChooser(client, parameters);
489}
490#endif
491
492void Chrome::runOpenPanel(Frame* frame, PassRefPtr<FileChooser> fileChooser)
493{
494    notifyPopupOpeningObservers();
495    m_client.runOpenPanel(frame, fileChooser);
496}
497
498void Chrome::loadIconForFiles(const Vector<String>& filenames, FileIconLoader* loader)
499{
500    m_client.loadIconForFiles(filenames, loader);
501}
502
503FloatSize Chrome::screenSize() const
504{
505    return m_client.screenSize();
506}
507
508FloatSize Chrome::availableScreenSize() const
509{
510    return m_client.availableScreenSize();
511}
512
513void Chrome::dispatchViewportPropertiesDidChange(const ViewportArguments& arguments) const
514{
515#if PLATFORM(IOS)
516    if (m_isDispatchViewportDataDidChangeSuppressed)
517        return;
518#endif
519    m_client.dispatchViewportPropertiesDidChange(arguments);
520}
521
522void Chrome::setCursor(const Cursor& cursor)
523{
524#if ENABLE(CURSOR_SUPPORT)
525    m_client.setCursor(cursor);
526#else
527    UNUSED_PARAM(cursor);
528#endif
529}
530
531void Chrome::setCursorHiddenUntilMouseMoves(bool hiddenUntilMouseMoves)
532{
533#if ENABLE(CURSOR_SUPPORT)
534    m_client.setCursorHiddenUntilMouseMoves(hiddenUntilMouseMoves);
535#else
536    UNUSED_PARAM(hiddenUntilMouseMoves);
537#endif
538}
539
540#if ENABLE(REQUEST_ANIMATION_FRAME)
541void Chrome::scheduleAnimation()
542{
543#if !USE(REQUEST_ANIMATION_FRAME_TIMER)
544    m_client.scheduleAnimation();
545#endif
546}
547#endif
548
549PlatformDisplayID Chrome::displayID() const
550{
551    return m_displayID;
552}
553
554void Chrome::windowScreenDidChange(PlatformDisplayID displayID)
555{
556    if (displayID == m_displayID)
557        return;
558
559    m_displayID = displayID;
560
561    for (Frame* frame = &m_page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
562        if (frame->document())
563            frame->document()->windowScreenDidChange(displayID);
564    }
565}
566
567// --------
568
569#if ENABLE(DASHBOARD_SUPPORT)
570void ChromeClient::annotatedRegionsChanged()
571{
572}
573#endif
574
575void ChromeClient::populateVisitedLinks()
576{
577}
578
579bool ChromeClient::shouldReplaceWithGeneratedFileForUpload(const String&, String&)
580{
581    return false;
582}
583
584String ChromeClient::generateReplacementFile(const String&)
585{
586    ASSERT_NOT_REACHED();
587    return String();
588}
589
590bool Chrome::selectItemWritingDirectionIsNatural()
591{
592    return m_client.selectItemWritingDirectionIsNatural();
593}
594
595bool Chrome::selectItemAlignmentFollowsMenuWritingDirection()
596{
597    return m_client.selectItemAlignmentFollowsMenuWritingDirection();
598}
599
600bool Chrome::hasOpenedPopup() const
601{
602    return m_client.hasOpenedPopup();
603}
604
605PassRefPtr<PopupMenu> Chrome::createPopupMenu(PopupMenuClient* client) const
606{
607    notifyPopupOpeningObservers();
608    return m_client.createPopupMenu(client);
609}
610
611PassRefPtr<SearchPopupMenu> Chrome::createSearchPopupMenu(PopupMenuClient* client) const
612{
613    notifyPopupOpeningObservers();
614    return m_client.createSearchPopupMenu(client);
615}
616
617bool Chrome::requiresFullscreenForVideoPlayback()
618{
619    return m_client.requiresFullscreenForVideoPlayback();
620}
621
622#if PLATFORM(IOS)
623// FIXME: Make argument, frame, a reference.
624void Chrome::didReceiveDocType(Frame* frame)
625{
626    ASSERT(frame);
627    if (!frame->isMainFrame())
628        return;
629
630    bool hasMobileDocType = false;
631    if (DocumentType* documentType = frame->document()->doctype())
632        hasMobileDocType = documentType->publicId().contains("xhtml mobile", false);
633    m_client.didReceiveMobileDocType(hasMobileDocType);
634}
635#endif
636
637void Chrome::registerPopupOpeningObserver(PopupOpeningObserver* observer)
638{
639    ASSERT(observer);
640    m_popupOpeningObservers.append(observer);
641}
642
643void Chrome::unregisterPopupOpeningObserver(PopupOpeningObserver* observer)
644{
645    size_t index = m_popupOpeningObservers.find(observer);
646    ASSERT(index != notFound);
647    m_popupOpeningObservers.remove(index);
648}
649
650void Chrome::notifyPopupOpeningObservers() const
651{
652    const Vector<PopupOpeningObserver*> observers(m_popupOpeningObservers);
653    for (size_t i = 0; i < observers.size(); ++i)
654        observers[i]->willOpenPopup();
655}
656
657} // namespace WebCore
658