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 "WebImage.h"
29#include "WebKitDOMDocumentPrivate.h"
30#include "WebKitFramePrivate.h"
31#include "WebKitMarshal.h"
32#include "WebKitPrivate.h"
33#include "WebKitScriptWorldPrivate.h"
34#include "WebKitURIRequestPrivate.h"
35#include "WebKitURIResponsePrivate.h"
36#include "WebKitWebPagePrivate.h"
37#include "WebProcess.h"
38#include <WebCore/Document.h>
39#include <WebCore/DocumentLoader.h>
40#include <WebCore/Frame.h>
41#include <WebCore/FrameView.h>
42#include <glib/gi18n-lib.h>
43#include <wtf/NeverDestroyed.h>
44#include <wtf/text/CString.h>
45
46using namespace WebKit;
47using namespace WebCore;
48
49enum {
50    DOCUMENT_LOADED,
51    SEND_REQUEST,
52
53    LAST_SIGNAL
54};
55
56enum {
57    PROP_0,
58
59    PROP_URI
60};
61
62struct _WebKitWebPagePrivate {
63    WebPage* webPage;
64
65    CString uri;
66};
67
68static guint signals[LAST_SIGNAL] = { 0, };
69
70WEBKIT_DEFINE_TYPE(WebKitWebPage, webkit_web_page, G_TYPE_OBJECT)
71
72typedef HashMap<WebFrame*, GRefPtr<WebKitFrame>> WebFrameMap;
73
74static WebFrameMap& webFrameMap()
75{
76    static NeverDestroyed<WebFrameMap> map;
77    return map;
78}
79
80static WebKitFrame* webkitFrameGetOrCreate(WebFrame* webFrame)
81{
82    GRefPtr<WebKitFrame> frame = webFrameMap().get(webFrame);
83    if (frame)
84        return frame.get();
85
86    frame = adoptGRef(webkitFrameCreate(webFrame));
87    webFrameMap().set(webFrame, frame);
88
89    return frame.get();
90}
91
92static CString getProvisionalURLForFrame(WebFrame* webFrame)
93{
94    DocumentLoader* documentLoader = webFrame->coreFrame()->loader().provisionalDocumentLoader();
95    if (!documentLoader->unreachableURL().isEmpty())
96        return documentLoader->unreachableURL().string().utf8();
97
98    return documentLoader->url().string().utf8();
99}
100
101static void webkitWebPageSetURI(WebKitWebPage* webPage, const CString& uri)
102{
103    if (webPage->priv->uri == uri)
104        return;
105
106    webPage->priv->uri = uri;
107    g_object_notify(G_OBJECT(webPage), "uri");
108}
109
110static void didStartProvisionalLoadForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKTypeRef*, const void *clientInfo)
111{
112    if (!WKBundleFrameIsMainFrame(frame))
113        return;
114
115    webkitWebPageSetURI(WEBKIT_WEB_PAGE(clientInfo), getProvisionalURLForFrame(toImpl(frame)));
116}
117
118static void didReceiveServerRedirectForProvisionalLoadForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKTypeRef* /* userData */, const void *clientInfo)
119{
120    if (!WKBundleFrameIsMainFrame(frame))
121        return;
122
123    webkitWebPageSetURI(WEBKIT_WEB_PAGE(clientInfo), getProvisionalURLForFrame(toImpl(frame)));
124}
125
126static void didSameDocumentNavigationForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKSameDocumentNavigationType, WKTypeRef* /* userData */, const void *clientInfo)
127{
128    if (!WKBundleFrameIsMainFrame(frame))
129        return;
130
131    webkitWebPageSetURI(WEBKIT_WEB_PAGE(clientInfo), toImpl(frame)->coreFrame()->document()->url().string().utf8());
132}
133
134static void didFinishDocumentLoadForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKTypeRef*, const void *clientInfo)
135{
136    if (!WKBundleFrameIsMainFrame(frame))
137        return;
138
139    g_signal_emit(WEBKIT_WEB_PAGE(clientInfo), signals[DOCUMENT_LOADED], 0);
140}
141
142static void willDestroyFrame(WKBundlePageRef, WKBundleFrameRef frame, const void* /* clientInfo */)
143{
144    webFrameMap().remove(toImpl(frame));
145}
146
147static void didClearWindowObjectForFrame(WKBundlePageRef, WKBundleFrameRef frame, WKBundleScriptWorldRef wkWorld, const void* clientInfo)
148{
149    if (WebKitScriptWorld* world = webkitScriptWorldGet(toImpl(wkWorld)))
150        webkitScriptWorldWindowObjectCleared(world, WEBKIT_WEB_PAGE(clientInfo), webkitFrameGetOrCreate(toImpl(frame)));
151}
152
153static void didInitiateLoadForResource(WKBundlePageRef page, WKBundleFrameRef frame, uint64_t identifier, WKURLRequestRef request, bool /* pageLoadIsProvisional */, const void*)
154{
155    ImmutableDictionary::MapType message;
156    message.set(String::fromUTF8("Page"), toImpl(page));
157    message.set(String::fromUTF8("Frame"), toImpl(frame));
158    message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
159    message.set(String::fromUTF8("Request"), toImpl(request));
160    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidInitiateLoadForResource"), ImmutableDictionary::create(WTF::move(message)).get());
161}
162
163static WKURLRequestRef willSendRequestForFrame(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, WKURLRequestRef wkRequest, WKURLResponseRef wkRedirectResponse, const void* clientInfo)
164{
165    GRefPtr<WebKitURIRequest> request = adoptGRef(webkitURIRequestCreateForResourceRequest(toImpl(wkRequest)->resourceRequest()));
166    const ResourceResponse& redirectResourceResponse = toImpl(wkRedirectResponse)->resourceResponse();
167    GRefPtr<WebKitURIResponse> redirectResponse = !redirectResourceResponse.isNull() ? adoptGRef(webkitURIResponseCreateForResourceResponse(redirectResourceResponse)) : nullptr;
168
169    gboolean returnValue;
170    g_signal_emit(WEBKIT_WEB_PAGE(clientInfo), signals[SEND_REQUEST], 0, request.get(), redirectResponse.get(), &returnValue);
171    if (returnValue)
172        return 0;
173
174    ResourceRequest resourceRequest;
175    webkitURIRequestGetResourceRequest(request.get(), resourceRequest);
176    resourceRequest.setInitiatingPageID(toImpl(page)->pageID());
177    RefPtr<API::URLRequest> newRequest = API::URLRequest::create(resourceRequest);
178
179    ImmutableDictionary::MapType message;
180    message.set(String::fromUTF8("Page"), toImpl(page));
181    message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
182    message.set(String::fromUTF8("Request"), newRequest.get());
183    if (!redirectResourceResponse.isNull())
184        message.set(String::fromUTF8("RedirectResponse"), toImpl(wkRedirectResponse));
185    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidSendRequestForResource"), ImmutableDictionary::create(WTF::move(message)).get());
186
187    return toAPI(newRequest.release().leakRef());
188}
189
190static void didReceiveResponseForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, WKURLResponseRef response, const void*)
191{
192    ImmutableDictionary::MapType message;
193    message.set(String::fromUTF8("Page"), toImpl(page));
194    message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
195    message.set(String::fromUTF8("Response"), toImpl(response));
196    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidReceiveResponseForResource"), ImmutableDictionary::create(WTF::move(message)).get());
197}
198
199static void didReceiveContentLengthForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, uint64_t length, const void*)
200{
201    ImmutableDictionary::MapType message;
202    message.set(String::fromUTF8("Page"), toImpl(page));
203    message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
204    message.set(String::fromUTF8("ContentLength"), API::UInt64::create(length));
205    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidReceiveContentLengthForResource"), ImmutableDictionary::create(WTF::move(message)).get());
206}
207
208static void didFinishLoadForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, const void*)
209{
210    ImmutableDictionary::MapType message;
211    message.set(String::fromUTF8("Page"), toImpl(page));
212    message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
213    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidFinishLoadForResource"), ImmutableDictionary::create(WTF::move(message)).get());
214}
215
216static void didFailLoadForResource(WKBundlePageRef page, WKBundleFrameRef, uint64_t identifier, WKErrorRef error, const void*)
217{
218    ImmutableDictionary::MapType message;
219    message.set(String::fromUTF8("Page"), toImpl(page));
220    message.set(String::fromUTF8("Identifier"), API::UInt64::create(identifier));
221    message.set(String::fromUTF8("Error"), toImpl(error));
222    WebProcess::shared().injectedBundle()->postMessage(String::fromUTF8("WebPage.DidFailLoadForResource"), ImmutableDictionary::create(WTF::move(message)).get());
223}
224
225static void webkitWebPageGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
226{
227    WebKitWebPage* webPage = WEBKIT_WEB_PAGE(object);
228
229    switch (propId) {
230    case PROP_URI:
231        g_value_set_string(value, webkit_web_page_get_uri(webPage));
232        break;
233    default:
234        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
235    }
236}
237
238static void webkit_web_page_class_init(WebKitWebPageClass* klass)
239{
240    GObjectClass* gObjectClass = G_OBJECT_CLASS(klass);
241
242    gObjectClass->get_property = webkitWebPageGetProperty;
243
244    /**
245     * WebKitWebPage:uri:
246     *
247     * The current active URI of the #WebKitWebPage.
248     */
249    g_object_class_install_property(
250        gObjectClass,
251        PROP_URI,
252        g_param_spec_string(
253            "uri",
254            _("URI"),
255            _("The current active URI of the web page"),
256            0,
257            WEBKIT_PARAM_READABLE));
258
259    /**
260     * WebKitWebPage::document-loaded:
261     * @web_page: the #WebKitWebPage on which the signal is emitted
262     *
263     * This signal is emitted when the DOM document of a #WebKitWebPage has been
264     * loaded.
265     *
266     * You can wait for this signal to get the DOM document with
267     * webkit_web_page_get_dom_document().
268     */
269    signals[DOCUMENT_LOADED] = g_signal_new(
270        "document-loaded",
271        G_TYPE_FROM_CLASS(klass),
272        G_SIGNAL_RUN_LAST,
273        0, 0, 0,
274        g_cclosure_marshal_VOID__VOID,
275        G_TYPE_NONE, 0);
276
277    /**
278     * WebKitWebPage::send-request:
279     * @web_page: the #WebKitWebPage on which the signal is emitted
280     * @request: a #WebKitURIRequest
281     * @redirected_response: a #WebKitURIResponse, or %NULL
282     *
283     * This signal is emitted when @request is about to be sent to
284     * the server. This signal can be used to modify the #WebKitURIRequest
285     * that will be sent to the server. You can also cancel the resource load
286     * operation by connecting to this signal and returning %TRUE.
287     *
288     * In case of a server redirection this signal is
289     * emitted again with the @request argument containing the new
290     * request to be sent to the server due to the redirection and the
291     * @redirected_response parameter containing the response
292     * received by the server for the initial request.
293     *
294     * Returns: %TRUE to stop other handlers from being invoked for the event.
295     *    %FALSE to continue emission of the event.
296     */
297    signals[SEND_REQUEST] = g_signal_new(
298        "send-request",
299        G_TYPE_FROM_CLASS(klass),
300        G_SIGNAL_RUN_LAST,
301        0,
302        g_signal_accumulator_true_handled, 0,
303        webkit_marshal_BOOLEAN__OBJECT_OBJECT,
304        G_TYPE_BOOLEAN, 2,
305        WEBKIT_TYPE_URI_REQUEST,
306        WEBKIT_TYPE_URI_RESPONSE);
307}
308
309WebKitWebPage* webkitWebPageCreate(WebPage* webPage)
310{
311    WebKitWebPage* page = WEBKIT_WEB_PAGE(g_object_new(WEBKIT_TYPE_WEB_PAGE, NULL));
312    page->priv->webPage = webPage;
313
314    WKBundlePageLoaderClientV7 loaderClient = {
315        {
316            7, // version
317            page, // clientInfo
318        },
319        didStartProvisionalLoadForFrame,
320        didReceiveServerRedirectForProvisionalLoadForFrame,
321        0, // didFailProvisionalLoadWithErrorForFrame
322        0, // didCommitLoadForFrame
323        didFinishDocumentLoadForFrame,
324        0, // didFinishLoadForFrame
325        0, // didFailLoadWithErrorForFrame
326        didSameDocumentNavigationForFrame,
327        0, // didReceiveTitleForFrame
328        0, // didFirstLayoutForFrame
329        0, // didFirstVisuallyNonEmptyLayoutForFrame
330        0, // didRemoveFrameFromHierarchy
331        0, // didDisplayInsecureContentForFrame
332        0, // didRunInsecureContentForFrame
333        didClearWindowObjectForFrame,
334        0, // didCancelClientRedirectForFrame
335        0, // willPerformClientRedirectForFrame
336        0, // didHandleOnloadEventsForFrame
337        0, // didLayoutForFrame
338        0, // didNewFirstVisuallyNonEmptyLayout
339        0, // didDetectXSSForFrame
340        0, // shouldGoToBackForwardListItem
341        0, // globalObjectIsAvailableForFrame
342        0, // willDisconnectDOMWindowExtensionFromGlobalObject
343        0, // didReconnectDOMWindowExtensionToGlobalObject
344        0, // willDestroyGlobalObjectForDOMWindowExtension
345        0, // didFinishProgress
346        0, // shouldForceUniversalAccessFromLocalURL
347        0, // didReceiveIntentForFrame_unavailable
348        0, // registerIntentServiceForFrame_unavailable
349        0, // didLayout
350        0, // featuresUsedInPage
351        0, // willLoadURLRequest
352        0, // willLoadDataRequest
353        willDestroyFrame
354    };
355    WKBundlePageSetPageLoaderClient(toAPI(webPage), &loaderClient.base);
356
357    WKBundlePageResourceLoadClientV1 resourceLoadClient = {
358        {
359            1, // version
360            page, // clientInfo
361        },
362        didInitiateLoadForResource,
363        willSendRequestForFrame,
364        didReceiveResponseForResource,
365        didReceiveContentLengthForResource,
366        didFinishLoadForResource,
367        didFailLoadForResource,
368        0, // shouldCacheResponse
369        0 // shouldUseCredentialStorage
370    };
371    WKBundlePageSetResourceLoadClient(toAPI(webPage), &resourceLoadClient.base);
372
373    return page;
374}
375
376void webkitWebPageDidReceiveMessage(WebKitWebPage* page, const String& messageName, ImmutableDictionary& message)
377{
378    if (messageName == String("GetSnapshot")) {
379        SnapshotOptions snapshotOptions = static_cast<SnapshotOptions>(static_cast<API::UInt64*>(message.get("SnapshotOptions"))->value());
380        uint64_t callbackID = static_cast<API::UInt64*>(message.get("CallbackID"))->value();
381        SnapshotRegion region = static_cast<SnapshotRegion>(static_cast<API::UInt64*>(message.get("SnapshotRegion"))->value());
382
383        RefPtr<WebImage> snapshotImage;
384        WebPage* webPage = page->priv->webPage;
385        if (WebCore::FrameView* frameView = webPage->mainFrameView()) {
386            WebCore::IntRect snapshotRect;
387            switch (region) {
388            case SnapshotRegionVisible:
389                snapshotRect = frameView->visibleContentRect();
390                break;
391            case SnapshotRegionFullDocument:
392                snapshotRect = WebCore::IntRect(WebCore::IntPoint(0, 0), frameView->contentsSize());
393                break;
394            default:
395                ASSERT_NOT_REACHED();
396            }
397            if (!snapshotRect.isEmpty())
398                snapshotImage = webPage->scaledSnapshotWithOptions(snapshotRect, 1, snapshotOptions | SnapshotOptionsShareable);
399        }
400
401        ImmutableDictionary::MapType messageReply;
402        messageReply.set("Page", webPage);
403        messageReply.set("CallbackID", API::UInt64::create(callbackID));
404        messageReply.set("Snapshot", snapshotImage);
405        WebProcess::shared().injectedBundle()->postMessage("WebPage.DidGetSnapshot", ImmutableDictionary::create(WTF::move(messageReply)).get());
406    } else
407        ASSERT_NOT_REACHED();
408}
409
410/**
411 * webkit_web_page_get_dom_document:
412 * @web_page: a #WebKitWebPage
413 *
414 * Get the #WebKitDOMDocument currently loaded in @web_page
415 *
416 * Returns: (transfer none): the #WebKitDOMDocument currently loaded, or %NULL
417 *    if no document is currently loaded.
418 */
419WebKitDOMDocument* webkit_web_page_get_dom_document(WebKitWebPage* webPage)
420{
421    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
422
423    Frame* coreFrame = webPage->priv->webPage->mainFrame();
424    if (!coreFrame)
425        return 0;
426
427    return kit(coreFrame->document());
428}
429
430/**
431 * webkit_web_page_get_id:
432 * @web_page: a #WebKitWebPage
433 *
434 * Get the identifier of the #WebKitWebPage
435 *
436 * Returns: the identifier of @web_page
437 */
438guint64 webkit_web_page_get_id(WebKitWebPage* webPage)
439{
440    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
441
442    return webPage->priv->webPage->pageID();
443}
444
445/**
446 * webkit_web_page_get_uri:
447 * @web_page: a #WebKitWebPage
448 *
449 * Returns the current active URI of @web_page.
450 *
451 * You can monitor the active URI by connecting to the notify::uri
452 * signal of @web_page.
453 *
454 * Returns: the current active URI of @web_view or %NULL if nothing has been
455 *    loaded yet.
456 */
457const gchar* webkit_web_page_get_uri(WebKitWebPage* webPage)
458{
459    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
460
461    return webPage->priv->uri.data();
462}
463
464/**
465 * webkit_web_page_get_main_frame:
466 * @web_page: a #WebKitWebPage
467 *
468 * Returns the main frame of a #WebKitWebPage.
469 *
470 * Returns: (transfer none): the #WebKitFrame that is the main frame of @web_page
471 *
472 * Since: 2.2
473 */
474WebKitFrame* webkit_web_page_get_main_frame(WebKitWebPage* webPage)
475{
476    g_return_val_if_fail(WEBKIT_IS_WEB_PAGE(webPage), 0);
477
478    return webkitFrameGetOrCreate(webPage->priv->webPage->mainWebFrame());
479}
480