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