1/*
2 * Copyright (C) 2012 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2,1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitWebPage.h"
22
23#include "ImageOptions.h"
24#include "ImmutableDictionary.h"
25#include "InjectedBundle.h"
26#include "WKBundleAPICast.h"
27#include "WKBundleFrame.h"
28#include "WebFrame.h"
29#include "WebImage.h"
30#include "WebKitDOMDocumentPrivate.h"
31#include "WebKitMarshal.h"
32#include "WebKitPrivate.h"
33#include "WebKitURIRequestPrivate.h"
34#include "WebKitURIResponsePrivate.h"
35#include "WebKitWebPagePrivate.h"
36#include "WebProcess.h"
37#include <WebCore/Document.h>
38#include <WebCore/DocumentLoader.h>
39#include <WebCore/Frame.h>
40#include <WebCore/FrameView.h>
41#include <glib/gi18n-lib.h>
42#include <wtf/text/CString.h>
43
44using namespace WebKit;
45using namespace WebCore;
46
47enum {
48    DOCUMENT_LOADED,
49    SEND_REQUEST,
50
51    LAST_SIGNAL
52};
53
54enum {
55    PROP_0,
56
57    PROP_URI
58};
59
60struct _WebKitWebPagePrivate {
61    WebPage* webPage;
62
63    CString uri;
64};
65
66static guint signals[LAST_SIGNAL] = { 0, };
67
68WEBKIT_DEFINE_TYPE(WebKitWebPage, webkit_web_page, G_TYPE_OBJECT)
69
70static CString getProvisionalURLForFrame(WebFrame* webFrame)
71{
72    DocumentLoader* documentLoader = webFrame->coreFrame()->loader()->provisionalDocumentLoader();
73    if (!documentLoader->unreachableURL().isEmpty())
74        return documentLoader->unreachableURL().string().utf8();
75
76    return documentLoader->url().string().utf8();
77}
78
79static void webkitWebPageSetURI(WebKitWebPage* webPage, const CString& uri)
80{
81    if (webPage->priv->uri == uri)
82        return;
83
84    webPage->priv->uri = uri;
85    g_object_notify(G_OBJECT(webPage), "uri");
86}
87
88static void didStartProvisionalLoadForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKTypeRef*, const void *clientInfo)
89{
90    if (!WKBundleFrameIsMainFrame(frame))
91        return;
92
93    webkitWebPageSetURI(WEBKIT_WEB_PAGE(clientInfo), getProvisionalURLForFrame(toImpl(frame)));
94}
95
96static void didReceiveServerRedirectForProvisionalLoadForFrame(WKBundlePageRef page, WKBundleFrameRef frame, WKTypeRef* userData, const void *clientInfo)
97{
98    if (!WKBundleFrameIsMainFrame(frame))
99        return;
100
101    webkitWebPageSetURI(WEBKIT_WEB_PAGE(clientInfo), getProvisionalURLForFrame(toImpl(frame)));
102}
103
104static void didSameDocumentNavigationForFrame(WKBundlePageRef page, WKBundleFrameRef frame, WKSameDocumentNavigationType type, WKTypeRef* userData, const void *clientInfo)
105{
106    if (!WKBundleFrameIsMainFrame(frame))
107        return;
108
109    webkitWebPageSetURI(WEBKIT_WEB_PAGE(clientInfo), toImpl(frame)->coreFrame()->document()->url().string().utf8());
110}
111
112static void didFinishDocumentLoadForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKTypeRef*, const void *clientInfo)
113{
114    if (!WKBundleFrameIsMainFrame(frame))
115        return;
116
117    g_signal_emit(WEBKIT_WEB_PAGE(clientInfo), signals[DOCUMENT_LOADED], 0);
118}
119
120static void didInitiateLoadForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, WKURLRequestRef request, bool pageLoadIsProvisional, const void*)
121{
122    ImmutableDictionary::MapType message;
123    message.set(String::fromUTF8("Page"), toImpl(page));
124    message.set(String::fromUTF8("Frame"), toImpl(frame));
125    message.set(String::fromUTF8("Identifier"), WebUInt64::create(identifier));
126    message.set(String::fromUTF8("Request"), toImpl(request));
127    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidInitiateLoadForResource"), ImmutableDictionary::adopt(message).get());
128}
129
130static WKURLRequestRef willSendRequestForFrame(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef wkRequest, WKURLResponseRef wkRedirectResponse, const void* clientInfo)
131{
132    GRefPtr<WebKitURIRequest> request = adoptGRef(webkitURIRequestCreateForResourceRequest(toImpl(wkRequest)->resourceRequest()));
133    GRefPtr<WebKitURIResponse> redirectResponse = wkRedirectResponse ? adoptGRef(webkitURIResponseCreateForResourceResponse(toImpl(wkRedirectResponse)->resourceResponse())) : 0;
134
135    gboolean returnValue;
136    g_signal_emit(WEBKIT_WEB_PAGE(clientInfo), signals[SEND_REQUEST], 0, request.get(), redirectResponse.get(), &returnValue);
137    if (returnValue)
138        return 0;
139
140    ResourceRequest resourceRequest;
141    webkitURIRequestGetResourceRequest(request.get(), resourceRequest);
142    RefPtr<WebURLRequest> newRequest = WebURLRequest::create(resourceRequest);
143
144    ImmutableDictionary::MapType message;
145    message.set(String::fromUTF8("Page"), toImpl(page));
146    message.set(String::fromUTF8("Identifier"), WebUInt64::create(identifier));
147    message.set(String::fromUTF8("Request"), newRequest.get());
148    if (!toImpl(wkRedirectResponse)->resourceResponse().isNull())
149        message.set(String::fromUTF8("RedirectResponse"), toImpl(wkRedirectResponse));
150    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidSendRequestForResource"), ImmutableDictionary::adopt(message).get());
151
152    return toAPI(newRequest.release().leakRef());
153}
154
155static void didReceiveResponseForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, WKURLResponseRef response, const void*)
156{
157    ImmutableDictionary::MapType message;
158    message.set(String::fromUTF8("Page"), toImpl(page));
159    message.set(String::fromUTF8("Identifier"), WebUInt64::create(identifier));
160    message.set(String::fromUTF8("Response"), toImpl(response));
161    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidReceiveResponseForResource"), ImmutableDictionary::adopt(message).get());
162}
163
164static void didReceiveContentLengthForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, uint64_t length, const void*)
165{
166    ImmutableDictionary::MapType message;
167    message.set(String::fromUTF8("Page"), toImpl(page));
168    message.set(String::fromUTF8("Identifier"), WebUInt64::create(identifier));
169    message.set(String::fromUTF8("ContentLength"), WebUInt64::create(length));
170    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidReceiveContentLengthForResource"), ImmutableDictionary::adopt(message).get());
171}
172
173static void didFinishLoadForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, const void*)
174{
175    ImmutableDictionary::MapType message;
176    message.set(String::fromUTF8("Page"), toImpl(page));
177    message.set(String::fromUTF8("Identifier"), WebUInt64::create(identifier));
178    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidFinishLoadForResource"), ImmutableDictionary::adopt(message).get());
179}
180
181static void didFailLoadForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, WKErrorRef error, const void*)
182{
183    ImmutableDictionary::MapType message;
184    message.set(String::fromUTF8("Page"), toImpl(page));
185    message.set(String::fromUTF8("Identifier"), WebUInt64::create(identifier));
186    message.set(String::fromUTF8("Error"), toImpl(error));
187    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidFailLoadForResource"), ImmutableDictionary::adopt(message).get());
188}
189
190static void webkitWebPageGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
191{
192    WebKitWebPage* webPage = WEBKIT_WEB_PAGE(object);
193
194    switch (propId) {
195    case PROP_URI:
196        g_value_set_string(value, webkit_web_page_get_uri(webPage));
197        break;
198    default:
199        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
200    }
201}
202
203static void webkit_web_page_class_init(WebKitWebPageClass* klass)
204{
205    GObjectClass* gObjectClass = G_OBJECT_CLASS(klass);
206
207    gObjectClass->get_property = webkitWebPageGetProperty;
208
209    /**
210     * WebKitWebPage:uri:
211     *
212     * The current active URI of the #WebKitWebPage.
213     */
214    g_object_class_install_property(
215        gObjectClass,
216        PROP_URI,
217        g_param_spec_string(
218            "uri",
219            _("URI"),
220            _("The current active URI of the web page"),
221            0,
222            WEBKIT_PARAM_READABLE));
223
224    /**
225     * WebKitWebPage::document-loaded:
226     * @web_page: the #WebKitWebPage on which the signal is emitted
227     *
228     * This signal is emitted when the DOM document of a #WebKitWebPage has been
229     * loaded.
230     *
231     * You can wait for this signal to get the DOM document with
232     * webkit_web_page_get_dom_document().
233     */
234    signals[DOCUMENT_LOADED] = g_signal_new(
235        "document-loaded",
236        G_TYPE_FROM_CLASS(klass),
237        G_SIGNAL_RUN_LAST,
238        0, 0, 0,
239        g_cclosure_marshal_VOID__OBJECT,
240        G_TYPE_NONE, 0);
241
242    /**
243     * WebKitWebPage::send-request:
244     * @web_page: the #WebKitWebPage on which the signal is emitted
245     * @request: a #WebKitURIRequest
246     * @redirected_response: a #WebKitURIResponse, or %NULL
247     *
248     * This signal is emitted when @request is about to be sent to
249     * the server. This signal can be used to modify the #WebKitURIRequest
250     * that will be sent to the server. You can also cancel the resource load
251     * operation by connecting to this signal and returning %TRUE.
252     *
253     * In case of a server redirection this signal is
254     * emitted again with the @request argument containing the new
255     * request to be sent to the server due to the redirection and the
256     * @redirected_response parameter containing the response
257     * received by the server for the initial request.
258     *
259     * Returns: %TRUE to stop other handlers from being invoked for the event.
260     *    %FALSE to continue emission of the event.
261     */
262    signals[SEND_REQUEST] = g_signal_new(
263        "send-request",
264        G_TYPE_FROM_CLASS(klass),
265        G_SIGNAL_RUN_LAST,
266        0,
267        g_signal_accumulator_true_handled, 0,
268        webkit_marshal_BOOLEAN__OBJECT_OBJECT,
269        G_TYPE_BOOLEAN, 2,
270        WEBKIT_TYPE_URI_REQUEST,
271        WEBKIT_TYPE_URI_RESPONSE);
272}
273
274WebKitWebPage* webkitWebPageCreate(WebPage* webPage)
275{
276    WebKitWebPage* page = WEBKIT_WEB_PAGE(g_object_new(WEBKIT_TYPE_WEB_PAGE, NULL));
277    page->priv->webPage = webPage;
278
279    WKBundlePageLoaderClient loaderClient = {
280        kWKBundlePageResourceLoadClientCurrentVersion,
281        page,
282        didStartProvisionalLoadForFrame,
283        didReceiveServerRedirectForProvisionalLoadForFrame,
284        0, // didFailProvisionalLoadWithErrorForFrame
285        0, // didCommitLoadForFrame
286        didFinishDocumentLoadForFrame,
287        0, // didFinishLoadForFrame
288        0, // didFailLoadWithErrorForFrame
289        didSameDocumentNavigationForFrame,
290        0, // didReceiveTitleForFrame
291        0, // didFirstLayoutForFrame
292        0, // didFirstVisuallyNonEmptyLayoutForFrame
293        0, // didRemoveFrameFromHierarchy
294        0, // didDisplayInsecureContentForFrame
295        0, // didRunInsecureContentForFrame
296        0, // didClearWindowObjectForFrame
297        0, // didCancelClientRedirectForFrame
298        0, // willPerformClientRedirectForFrame
299        0, // didHandleOnloadEventsForFrame
300        0, // didLayoutForFrame
301        0, // didNewFirstVisuallyNonEmptyLayout
302        0, // didDetectXSSForFrame
303        0, // shouldGoToBackForwardListItem
304        0, // globalObjectIsAvailableForFrame
305        0, // willDisconnectDOMWindowExtensionFromGlobalObject
306        0, // didReconnectDOMWindowExtensionToGlobalObject
307        0, // willDestroyGlobalObjectForDOMWindowExtension
308        0, // didFinishProgress
309        0, // shouldForceUniversalAccessFromLocalURL
310        0, // didReceiveIntentForFrame_unavailable
311        0, // registerIntentServiceForFrame_unavailable
312        0, // didLayout
313        0, // featuresUsedInPage
314    };
315    WKBundlePageSetPageLoaderClient(toAPI(webPage), &loaderClient);
316
317    WKBundlePageResourceLoadClient resourceLoadClient = {
318        kWKBundlePageResourceLoadClientCurrentVersion,
319        page,
320        didInitiateLoadForResource,
321        willSendRequestForFrame,
322        didReceiveResponseForResource,
323        didReceiveContentLengthForResource,
324        didFinishLoadForResource,
325        didFailLoadForResource,
326        0, // shouldCacheResponse
327        0 // shouldUseCredentialStorage
328    };
329    WKBundlePageSetResourceLoadClient(toAPI(webPage), &resourceLoadClient);
330
331    return page;
332}
333
334void webkitWebPageDidReceiveMessage(WebKitWebPage* page, const String& messageName, ImmutableDictionary& message)
335{
336    if (messageName == String("GetSnapshot")) {
337        SnapshotOptions snapshotOptions = static_cast<SnapshotOptions>(static_cast<WebUInt64*>(message.get("SnapshotOptions"))->value());
338        uint64_t callbackID = static_cast<WebUInt64*>(message.get("CallbackID"))->value();
339        SnapshotRegion region = static_cast<SnapshotRegion>(static_cast<WebUInt64*>(message.get("SnapshotRegion"))->value());
340
341        RefPtr<WebImage> snapshotImage;
342        WebPage* webPage = page->priv->webPage;
343        if (WebCore::FrameView* frameView = webPage->mainFrameView()) {
344            WebCore::IntRect snapshotRect;
345            switch (region) {
346            case SnapshotRegionVisible:
347                snapshotRect = frameView->visibleContentRect(WebCore::ScrollableArea::ExcludeScrollbars);
348                break;
349            case SnapshotRegionFullDocument:
350                snapshotRect = WebCore::IntRect(WebCore::IntPoint(0, 0), frameView->contentsSize());
351                break;
352            default:
353                ASSERT_NOT_REACHED();
354            }
355            if (!snapshotRect.isEmpty())
356                snapshotImage = webPage->scaledSnapshotWithOptions(snapshotRect, 1, snapshotOptions | SnapshotOptionsShareable);
357        }
358
359        ImmutableDictionary::MapType messageReply;
360        messageReply.set("Page", webPage);
361        messageReply.set("CallbackID", WebUInt64::create(callbackID));
362        messageReply.set("Snapshot", snapshotImage);
363        WebProcess::shared().injectedBundle()->postMessage("WebPage.DidGetSnapshot", ImmutableDictionary::adopt(messageReply).get());
364    } else
365        ASSERT_NOT_REACHED();
366}
367
368/**
369 * webkit_web_page_get_dom_document:
370 * @web_page: a #WebKitWebPage
371 *
372 * Get the #WebKitDOMDocument currently loaded in @web_page
373 *
374 * Returns: the #WebKitDOMDocument currently loaded, or %NULL
375 *    if no document is currently loaded.
376 */
377WebKitDOMDocument* webkit_web_page_get_dom_document(WebKitWebPage* webPage)
378{
379    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
380
381    Frame* coreFrame = webPage->priv->webPage->mainFrame();
382    if (!coreFrame)
383        return 0;
384
385    return kit(coreFrame->document());
386}
387
388/**
389 * webkit_web_page_get_id:
390 * @web_page: a #WebKitWebPage
391 *
392 * Get the identifier of the #WebKitWebPage
393 *
394 * Returns: the identifier of @web_page
395 */
396guint64 webkit_web_page_get_id(WebKitWebPage* webPage)
397{
398    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
399
400    return webPage->priv->webPage->pageID();
401}
402
403/**
404 * webkit_web_page_get_uri:
405 * @web_page: a #WebKitWebPage
406 *
407 * Returns the current active URI of @web_page.
408 *
409 * You can monitor the active URI by connecting to the notify::uri
410 * signal of @web_page.
411 *
412 * Returns: the current active URI of @web_view or %NULL if nothing has been
413 *    loaded yet.
414 */
415const gchar* webkit_web_page_get_uri(WebKitWebPage* webPage)
416{
417    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
418
419    return webPage->priv->uri.data();
420}
421