// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "config.h" #include #include #include "Chrome.h" #include "CString.h" #include "Document.h" #include "DocumentLoader.h" #include "HTMLAppletElement.h" #include "HTMLFormElement.h" // needed by FormState.h #include "HTMLNames.h" #include "FormState.h" #include "FrameLoader.h" #include "FrameLoadRequest.h" #include "MIMETypeRegistry.h" #include "MouseEvent.h" #include "Page.h" #include "PlatformString.h" #include "PluginData.h" #include "StringExtras.h" #include "WindowFeatures.h" #undef LOG #include "base/basictypes.h" #include "base/logging.h" #include "base/string_util.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" #if defined(OS_WIN) #include "webkit/activex_shim/activex_shared.h" #endif #include "webkit/api/public/WebForm.h" #include "webkit/api/public/WebURL.h" #include "webkit/api/public/WebURLError.h" #include "webkit/api/public/WebVector.h" #include "webkit/api/src/WebPluginContainerImpl.h" #include "webkit/api/src/WrappedResourceRequest.h" #include "webkit/api/src/WrappedResourceResponse.h" #include "webkit/glue/glue_util.h" #include "webkit/glue/plugins/plugin_list.h" #include "webkit/glue/webdatasource_impl.h" #include "webkit/glue/webdevtoolsagent_impl.h" #include "webkit/glue/webframe_impl.h" #include "webkit/glue/webframeloaderclient_impl.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/webplugin_delegate.h" #include "webkit/glue/webplugin_impl.h" #include "webkit/glue/webview_delegate.h" #include "webkit/glue/webview_impl.h" using namespace WebCore; using base::Time; using base::TimeDelta; using WebKit::WebData; using WebKit::WebNavigationType; using WebKit::WebNavigationPolicy; using WebKit::WebPluginContainerImpl; using WebKit::WebString; using WebKit::WebURL; using WebKit::WebURLError; using WebKit::WebURLRequest; using WebKit::WebVector; using WebKit::WrappedResourceRequest; using WebKit::WrappedResourceResponse; // Domain for internal error codes. static const char kInternalErrorDomain[] = "webkit_glue"; // An internal error code. Used to note a policy change error resulting from // dispatchDecidePolicyForMIMEType not passing the PolicyUse option. enum { ERR_POLICY_CHANGE = -10000, }; WebFrameLoaderClient::WebFrameLoaderClient(WebFrameImpl* frame) : webframe_(frame), has_representation_(false), sent_initial_response_to_plugin_(false), next_navigation_policy_(WebKit::WebNavigationPolicyIgnore) { } WebFrameLoaderClient::~WebFrameLoaderClient() { } void WebFrameLoaderClient::frameLoaderDestroyed() { // When the WebFrame was created, it had an extra reference given to it on // behalf of the Frame. Since the WebFrame owns us, this extra ref also // serves to keep us alive until the FrameLoader is done with us. The // FrameLoader calls this method when it's going away. Therefore, we balance // out that extra reference, which may cause 'this' to be deleted. webframe_->Closing(); webframe_->Release(); } void WebFrameLoaderClient::windowObjectCleared() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->WindowObjectCleared(webframe_); WebDevToolsAgentImpl* tools_agent = webview->GetWebDevToolsAgentImpl(); if (tools_agent) tools_agent->WindowObjectCleared(webframe_); } void WebFrameLoaderClient::documentElementAvailable() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DocumentElementAvailable(webframe_); } void WebFrameLoaderClient::didCreateScriptContextForFrame() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidCreateScriptContextForFrame(webframe_); } void WebFrameLoaderClient::didDestroyScriptContextForFrame() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidDestroyScriptContextForFrame(webframe_); } void WebFrameLoaderClient::didCreateIsolatedScriptContext() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidCreateIsolatedScriptContext(webframe_); } void WebFrameLoaderClient::didPerformFirstNavigation() const { } void WebFrameLoaderClient::registerForIconNotification(bool listen){ } bool WebFrameLoaderClient::hasWebView() const { return webframe_->GetWebViewImpl() != NULL; } bool WebFrameLoaderClient::hasFrameView() const { // The Mac port has this notion of a WebFrameView, which seems to be // some wrapper around an NSView. Since our equivalent is HWND, I guess // we have a "frameview" whenever we have the toplevel HWND. return webframe_->GetWebViewImpl() != NULL; } void WebFrameLoaderClient::makeDocumentView() { webframe_->CreateFrameView(); } void WebFrameLoaderClient::makeRepresentation(DocumentLoader*) { has_representation_ = true; } void WebFrameLoaderClient::forceLayout() { // FIXME } void WebFrameLoaderClient::forceLayoutForNonHTML() { // FIXME } void WebFrameLoaderClient::setCopiesOnScroll() { // FIXME } void WebFrameLoaderClient::detachedFromParent2() { // Nothing to do here. } void WebFrameLoaderClient::detachedFromParent3() { // Close down the proxy. The purpose of this change is to make the // call to ScriptController::clearWindowShell a no-op when called from // Frame::pageDestroyed. Without this change, this call to clearWindowShell // will cause a crash. If you remove/modify this, just ensure that you can // go to a page and then navigate to a new page without getting any asserts // or crashes. webframe_->frame()->script()->proxy()->clearForClose(); } // This function is responsible for associating the |identifier| with a given // subresource load. The following functions that accept an |identifier| are // called for each subresource, so they should not be dispatched to the // WebFrame. void WebFrameLoaderClient::assignIdentifierToInitialRequest( unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request) { WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) { WrappedResourceRequest webreq(request); d->AssignIdentifierToRequest(webframe_, identifier, webreq); } } // Determines whether the request being loaded by |loader| is a frame or a // subresource. A subresource in this context is anything other than a frame -- // this includes images and xmlhttp requests. It is important to note that a // subresource is NOT limited to stuff loaded through the frame's subresource // loader. Synchronous xmlhttp requests for example, do not go through the // subresource loader, but we still label them as TargetIsSubResource. // // The important edge cases to consider when modifying this function are // how synchronous resource loads are treated during load/unload threshold. static ResourceRequest::TargetType DetermineTargetTypeFromLoader( DocumentLoader* loader) { if (loader == loader->frameLoader()->provisionalDocumentLoader()) { if (loader->frameLoader()->isLoadingMainFrame()) { return ResourceRequest::TargetIsMainFrame; } else { return ResourceRequest::TargetIsSubFrame; } } return ResourceRequest::TargetIsSubResource; } void WebFrameLoaderClient::dispatchWillSendRequest( DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirect_response) { if (loader) { // We want to distinguish between a request for a document to be loaded into // the main frame, a sub-frame, or the sub-objects in that document. request.setTargetType(DetermineTargetTypeFromLoader(loader)); } // FrameLoader::loadEmptyDocumentSynchronously() creates an empty document // with no URL. We don't like that, so we'll rename it to about:blank. if (request.url().isEmpty()) request.setURL(KURL("about:blank")); if (request.firstPartyForCookies().isEmpty()) request.setFirstPartyForCookies(KURL("about:blank")); // Give the delegate a crack at the request. WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) { WrappedResourceRequest webreq(request); WrappedResourceResponse webresp(redirect_response); d->WillSendRequest(webframe_, identifier, &webreq, webresp); } } bool WebFrameLoaderClient::shouldUseCredentialStorage(DocumentLoader*, unsigned long identifier) { // FIXME // Intended to pass through to a method on the resource load delegate. // If implemented, that method controls whether the browser should ask the // networking layer for a stored default credential for the page (say from // the Mac OS keychain). If the method returns false, the user should be // presented with an authentication challenge whether or not the networking // layer has a credential stored. // This returns true for backward compatibility: the ability to override the // system credential store is new. (Actually, not yet fully implemented in // WebKit, as of this writing.) return true; } void WebFrameLoaderClient::dispatchDidReceiveAuthenticationChallenge( DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) { // FIXME } void WebFrameLoaderClient::dispatchDidCancelAuthenticationChallenge( DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) { // FIXME } void WebFrameLoaderClient::dispatchDidReceiveResponse(DocumentLoader* loader, unsigned long identifier, const ResourceResponse& response) { WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) { WrappedResourceResponse webresp(response); d->DidReceiveResponse(webframe_, identifier, webresp); } } void WebFrameLoaderClient::dispatchDidReceiveContentLength( DocumentLoader* loader, unsigned long identifier, int length_received) { } // Called when a particular resource load completes void WebFrameLoaderClient::dispatchDidFinishLoading(DocumentLoader* loader, unsigned long identifier) { WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) d->DidFinishLoading(webframe_, identifier); } void WebFrameLoaderClient::dispatchDidFailLoading(DocumentLoader* loader, unsigned long identifier, const ResourceError& error) { WebViewImpl* webview = webframe_->GetWebViewImpl(); if (webview && webview->delegate()) { webview->delegate()->DidFailLoadingWithError( webframe_, identifier, webkit_glue::ResourceErrorToWebURLError(error)); } } void WebFrameLoaderClient::dispatchDidFinishDocumentLoad() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); DocumentLoader* documentLoader = webframe_->frame()->loader()->activeDocumentLoader(); WebDataSourceImpl* data_source = WebDataSourceImpl::FromLoader(documentLoader); // A frame may be reused. This call ensures we don't hold on to our password // listeners and their associated HTMLInputElements. webframe_->ClearPasswordListeners(); if (d) d->DidFinishDocumentLoadForFrame(webview, webframe_); } bool WebFrameLoaderClient::dispatchDidLoadResourceFromMemoryCache( DocumentLoader* loader, const ResourceRequest& request, const ResourceResponse& response, int length) { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); bool result = false; if (d) { WrappedResourceRequest webreq(request); WrappedResourceResponse webresp(response); result = d->DidLoadResourceFromMemoryCache(webview, webreq, webresp, webframe_); } return result; } void WebFrameLoaderClient::dispatchDidLoadResourceByXMLHttpRequest( unsigned long identifier, const ScriptString& source) { } void WebFrameLoaderClient::dispatchDidHandleOnloadEvents() { // During the onload event of a subframe, the subframe can be removed. In // that case, it has no page. This is covered by // LayoutTests/fast/dom/replaceChild.html if (!webframe_->frame()->page()) return; WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidHandleOnloadEventsForFrame(webview, webframe_); } // Redirect Tracking // ================= // We want to keep track of the chain of redirects that occur during page // loading. There are two types of redirects, server redirects which are HTTP // response codes, and client redirects which are document.location= and meta // refreshes. // // This outlines the callbacks that we get in different redirect situations, // and how each call modifies the redirect chain. // // Normal page load // ---------------- // dispatchDidStartProvisionalLoad() -> adds URL to the redirect list // dispatchDidCommitLoad() -> DISPATCHES & clears list // // Server redirect (success) // ------------------------- // dispatchDidStartProvisionalLoad() -> adds source URL // dispatchDidReceiveServerRedirectForProvisionalLoad() -> adds dest URL // dispatchDidCommitLoad() -> DISPATCHES // // Client redirect (success) // ------------------------- // (on page) // dispatchWillPerformClientRedirect() -> saves expected redirect // dispatchDidStartProvisionalLoad() -> appends redirect source (since // it matches the expected redirect) // and the current page as the dest) // dispatchDidCancelClientRedirect() -> clears expected redirect // dispatchDidCommitLoad() -> DISPATCHES // // Client redirect (cancelled) // (e.g meta-refresh trumped by manual doc.location change, or just cancelled // because a link was clicked that requires the meta refresh to be rescheduled // (the SOURCE URL may have changed). // --------------------------- // dispatchDidCancelClientRedirect() -> clears expected redirect // dispatchDidStartProvisionalLoad() -> adds only URL to redirect list // dispatchDidCommitLoad() -> DISPATCHES & clears list // rescheduled ? dispatchWillPerformClientRedirect() -> saves expected redirect // : nothing // Client redirect (failure) // ------------------------- // (on page) // dispatchWillPerformClientRedirect() -> saves expected redirect // dispatchDidStartProvisionalLoad() -> appends redirect source (since // it matches the expected redirect) // and the current page as the dest) // dispatchDidCancelClientRedirect() // dispatchDidFailProvisionalLoad() // // Load 1 -> Server redirect to 2 -> client redirect to 3 -> server redirect to 4 // ------------------------------------------------------------------------------ // dispatchDidStartProvisionalLoad() -> adds source URL 1 // dispatchDidReceiveServerRedirectForProvisionalLoad() -> adds dest URL 2 // dispatchDidCommitLoad() -> DISPATCHES 1+2 // -- begin client redirect and NEW DATA SOURCE // dispatchWillPerformClientRedirect() -> saves expected redirect // dispatchDidStartProvisionalLoad() -> appends URL 2 and URL 3 // dispatchDidReceiveServerRedirectForProvisionalLoad() -> appends destination URL 4 // dispatchDidCancelClientRedirect() -> clears expected redirect // dispatchDidCommitLoad() -> DISPATCHES // // Interesting case with multiple location changes involving anchors. // Load page 1 containing future client-redirect (back to 1, e.g meta refresh) > Click // on a link back to the same page (i.e an anchor href) > // client-redirect finally fires (with new source, set to 1#anchor) // ----------------------------------------------------------------------------- // dispatchWillPerformClientRedirect(non-zero 'interval' param) -> saves expected redirect // -- click on anchor href // dispatchDidCancelClientRedirect() -> clears expected redirect // dispatchDidStartProvisionalLoad() -> adds 1#anchor source // dispatchDidCommitLoad() -> DISPATCHES 1#anchor // dispatchWillPerformClientRedirect() -> saves exp. source (1#anchor) // -- redirect timer fires // dispatchDidStartProvisionalLoad() -> appends 1#anchor (src) and 1 (dest) // dispatchDidCancelClientRedirect() -> clears expected redirect // dispatchDidCommitLoad() -> DISPATCHES 1#anchor + 1 // void WebFrameLoaderClient::dispatchDidReceiveServerRedirectForProvisionalLoad() { WebDataSourceImpl* ds = webframe_->GetProvisionalDataSourceImpl(); if (!ds) { NOTREACHED() << "Got a server redirect when there is no provisional DS"; return; } // The server redirect may have been blocked. if (ds->request().isNull()) return; // A provisional load should have started already, which should have put an // entry in our redirect chain. DCHECK(ds->HasRedirectChain()); // The URL of the destination is on the provisional data source. We also need // to update the redirect chain to account for this addition (we do this // before the callback so the callback can look at the redirect chain to see // what happened). ds->AppendRedirect(ds->request().url()); // Dispatch callback WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidReceiveProvisionalLoadServerRedirect(webview, webframe_); } // Called on both success and failure of a client redirect. void WebFrameLoaderClient::dispatchDidCancelClientRedirect() { // No longer expecting a client redirect. WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview ? webview->delegate() : NULL; if (d) { expected_client_redirect_src_ = GURL(); expected_client_redirect_dest_ = GURL(); d->DidCancelClientRedirect(webview, webframe_); } // No need to clear the redirect chain, since that data source has already // been deleted by the time this function is called. } void WebFrameLoaderClient::dispatchWillPerformClientRedirect(const KURL& url, double interval, double fire_date) { // Tells dispatchDidStartProvisionalLoad that if it sees this item it is a // redirect and the source item should be added as the start of the chain. WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview ? webview->delegate() : NULL; if (d) { expected_client_redirect_src_ = webframe_->url(); expected_client_redirect_dest_ = webkit_glue::KURLToGURL(url); // TODO(timsteele): bug 1135512. Webkit does not properly notify us of // cancelling http > file client redirects. Since the FrameLoader's policy // is to never carry out such a navigation anyway, the best thing we can do // for now to not get confused is ignore this notification. if (expected_client_redirect_dest_.SchemeIsFile() && (expected_client_redirect_src_.SchemeIs("http") || expected_client_redirect_src_.SchemeIsSecure())) { expected_client_redirect_src_ = GURL(); expected_client_redirect_dest_ = GURL(); return; } d->WillPerformClientRedirect(webview, webframe_, expected_client_redirect_src_, expected_client_redirect_dest_, static_cast(interval), static_cast(fire_date)); } } void WebFrameLoaderClient::dispatchDidChangeLocationWithinPage() { // Anchor fragment navigations are not normal loads, so we need to synthesize // some events for our delegate. WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidStartLoading(webview); WebDataSourceImpl* ds = webframe_->GetDataSourceImpl(); DCHECK(ds) << "DataSource NULL when navigating to reference fragment"; if (ds) { GURL url = ds->request().url(); GURL chain_end = ds->GetEndOfRedirectChain(); ds->ClearRedirectChain(); // Figure out if this location change is because of a JS-initiated client // redirect (e.g onload/setTimeout document.location.href=). // TODO(timsteele): (bugs 1085325, 1046841) We don't get proper redirect // performed/cancelled notifications across anchor navigations, so the // other redirect-tracking code in this class (see dispatch*ClientRedirect() // and dispatchDidStartProvisionalLoad) is insufficient to catch and // properly flag these transitions. Once a proper fix for this bug is // identified and applied the following block may no longer be required. bool was_client_redirect = ((url == expected_client_redirect_dest_) && (chain_end == expected_client_redirect_src_)) || (NavigationGestureForLastLoad() == NavigationGestureAuto); if (was_client_redirect) { if (d) d->DidCompleteClientRedirect(webview, webframe_, chain_end); ds->AppendRedirect(chain_end); // Make sure we clear the expected redirect since we just effectively // completed it. expected_client_redirect_src_ = GURL(); expected_client_redirect_dest_ = GURL(); } // Regardless of how we got here, we are navigating to a URL so we need to // add it to the redirect chain. ds->AppendRedirect(url); } bool is_new_navigation; webview->DidCommitLoad(&is_new_navigation); if (d) { d->DidChangeLocationWithinPageForFrame(webview, webframe_, is_new_navigation); } if (d) d->DidStopLoading(webview); } void WebFrameLoaderClient::dispatchWillClose() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->WillCloseFrame(webview, webframe_); } void WebFrameLoaderClient::dispatchDidReceiveIcon() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidReceiveIconForFrame(webview, webframe_); } void WebFrameLoaderClient::dispatchDidStartProvisionalLoad() { // In case a redirect occurs, we need this to be set so that the redirect // handling code can tell where the redirect came from. Server redirects // will occur on the provisional load, so we need to keep track of the most // recent provisional load URL. // See dispatchDidReceiveServerRedirectForProvisionalLoad. WebDataSourceImpl* ds = webframe_->GetProvisionalDataSourceImpl(); if (!ds) { NOTREACHED() << "Attempting to provisional load but there isn't one"; return; } GURL url = ds->request().url(); // Since the provisional load just started, we should have not gotten // any redirects yet. DCHECK(!ds->HasRedirectChain()); WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); // If this load is what we expected from a client redirect, treat it as a // redirect from that original page. The expected redirect urls will be // cleared by DidCancelClientRedirect. bool completing_client_redirect = false; if (expected_client_redirect_src_.is_valid()) { // expected_client_redirect_dest_ could be something like // "javascript:history.go(-1)" thus we need to exclude url starts with // "javascript:". See bug: 1080873 DCHECK(expected_client_redirect_dest_.SchemeIs("javascript") || expected_client_redirect_dest_ == url); ds->AppendRedirect(expected_client_redirect_src_); completing_client_redirect = true; } ds->AppendRedirect(url); if (d) { // As the comment for DidCompleteClientRedirect in webview_delegate.h // points out, whatever information its invocation contains should only // be considered relevant until the next provisional load has started. // So we first tell the delegate that the load started, and then tell it // about the client redirect the load is responsible for completing. d->DidStartProvisionalLoadForFrame(webview, webframe_, NavigationGestureForLastLoad()); if (completing_client_redirect) d->DidCompleteClientRedirect(webview, webframe_, expected_client_redirect_src_); } } void WebFrameLoaderClient::dispatchDidReceiveTitle(const String& title) { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) { d->DidReceiveTitle(webview, webkit_glue::StringToStdWString(title), webframe_); } } void WebFrameLoaderClient::dispatchDidCommitLoad() { WebViewImpl* webview = webframe_->GetWebViewImpl(); bool is_new_navigation; webview->DidCommitLoad(&is_new_navigation); WebViewDelegate* d = webview->delegate(); if (d) d->DidCommitLoadForFrame(webview, webframe_, is_new_navigation); WebDevToolsAgentImpl* tools_agent = webview->GetWebDevToolsAgentImpl(); if (tools_agent) { tools_agent->DidCommitLoadForFrame(webview, webframe_, is_new_navigation); } } void WebFrameLoaderClient::dispatchDidFailProvisionalLoad( const ResourceError& error) { // If a policy change occured, then we do not want to inform the plugin // delegate. See bug 907789 for details. if (error.domain() == kInternalErrorDomain && error.errorCode() == ERR_POLICY_CHANGE) { webframe_->DidFail(cancelledError(error.failingURL()), true); } else { webframe_->DidFail(error, true); WebPluginDelegate* plg_delegate = webframe_->plugin_delegate(); if (plg_delegate) plg_delegate->DidFinishLoadWithReason(NPRES_NETWORK_ERR); } } void WebFrameLoaderClient::dispatchDidFailLoad(const ResourceError& error) { webframe_->DidFail(error, false); WebPluginDelegate* plg_delegate = webframe_->plugin_delegate(); if (plg_delegate) plg_delegate->DidFinishLoadWithReason(NPRES_NETWORK_ERR); // Don't clear the redirect chain, this will happen in the middle of client // redirects, and we need the context. The chain will be cleared when the // provisional load succeeds or fails, not the "real" one. } void WebFrameLoaderClient::dispatchDidFinishLoad() { DocumentLoader* documentLoader = webframe_->frame()->loader()->activeDocumentLoader(); WebDataSourceImpl* dataSource = WebDataSourceImpl::FromLoader(documentLoader); WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->DidFinishLoadForFrame(webview, webframe_); WebPluginDelegate* plg_delegate = webframe_->plugin_delegate(); if (plg_delegate) plg_delegate->DidFinishLoadWithReason(NPRES_DONE); // Don't clear the redirect chain, this will happen in the middle of client // redirects, and we need the context. The chain will be cleared when the // provisional load succeeds or fails, not the "real" one. } void WebFrameLoaderClient::dispatchDidFirstLayout() { } void WebFrameLoaderClient::dispatchDidFirstVisuallyNonEmptyLayout() { // FIXME: called when webkit finished layout of a page that was visually // non-empty. // All resources have not necessarily finished loading. } Frame* WebFrameLoaderClient::dispatchCreatePage() { struct WebCore::WindowFeatures features; Page* new_page = webframe_->frame()->page()->chrome()->createWindow( webframe_->frame(), FrameLoadRequest(), features); // Make sure that we have a valid disposition. This should have been set in // the preceeding call to dispatchDecidePolicyForNewWindowAction. DCHECK(next_navigation_policy_ != WebKit::WebNavigationPolicyIgnore); WebNavigationPolicy policy = next_navigation_policy_; next_navigation_policy_ = WebKit::WebNavigationPolicyIgnore; // createWindow can return NULL (e.g., popup blocker denies the window). if (!new_page) return NULL; WebViewImpl::FromPage(new_page)->set_initial_navigation_policy(policy); return new_page->mainFrame(); } void WebFrameLoaderClient::dispatchShow() { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) d->show(webview->initial_navigation_policy()); } static bool TreatAsAttachment(const ResourceResponse& response) { const String& content_disposition = response.httpHeaderField("Content-Disposition"); if (content_disposition.isEmpty()) return false; // Some broken sites just send // Content-Disposition: ; filename="file" // screen those out here. if (content_disposition.startsWith(";")) return false; if (content_disposition.startsWith("inline", false)) return false; // Some broken sites just send // Content-Disposition: filename="file" // without a disposition token... screen those out. if (content_disposition.startsWith("filename", false)) return false; // Also in use is Content-Disposition: name="file" if (content_disposition.startsWith("name", false)) return false; // We have a content-disposition of "attachment" or unknown. // RFC 2183, section 2.8 says that an unknown disposition // value should be treated as "attachment" return true; } void WebFrameLoaderClient::dispatchDecidePolicyForMIMEType( FramePolicyFunction function, const String& mime_type, const ResourceRequest&) { const ResourceResponse& response = webframe_->frame()->loader()->activeDocumentLoader()->response(); PolicyAction action; int status_code = response.httpStatusCode(); if (status_code == 204 || status_code == 205) { // The server does not want us to replace the page contents. action = PolicyIgnore; } else if (TreatAsAttachment(response)) { // The server wants us to download instead of replacing the page contents. // Downloading is handled by the embedder, but we still get the initial // response so that we can ignore it and clean up properly. action = PolicyIgnore; } else if (!canShowMIMEType(mime_type)) { // Make sure that we can actually handle this type internally. action = PolicyIgnore; } else { // OK, we will render this page. action = PolicyUse; } // NOTE: ERR_POLICY_CHANGE will be generated when action is not PolicyUse. (webframe_->frame()->loader()->*function)(action); } void WebFrameLoaderClient::dispatchDecidePolicyForNewWindowAction( WebCore::FramePolicyFunction function, const WebCore::NavigationAction& action, const WebCore::ResourceRequest& request, PassRefPtr form_state, const WebCore::String& frame_name) { WebNavigationPolicy navigation_policy; if (!ActionSpecifiesNavigationPolicy(action, &navigation_policy)) navigation_policy = WebKit::WebNavigationPolicyNewForegroundTab; PolicyAction policy_action; if (navigation_policy == WebKit::WebNavigationPolicyDownload) { policy_action = PolicyDownload; } else { policy_action = PolicyUse; // Remember the disposition for when dispatchCreatePage is called. It is // unfortunate that WebCore does not provide us with any context when // creating or showing the new window that would allow us to avoid having // to keep this state. next_navigation_policy_ = navigation_policy; } (webframe_->frame()->loader()->*function)(policy_action); } void WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction( WebCore::FramePolicyFunction function, const WebCore::NavigationAction& action, const WebCore::ResourceRequest& request, PassRefPtr form_state) { PolicyAction policy_action = PolicyIgnore; WebViewImpl* wv = webframe_->GetWebViewImpl(); WebViewDelegate* d = wv->delegate(); // It is valid for this function to be invoked in code paths where the // the webview is closed. // The NULL check here is to fix a crash that seems strange // (see - https://bugs.webkit.org/show_bug.cgi?id=23554). if (d && !request.url().isNull()) { WebNavigationPolicy navigation_policy = WebKit::WebNavigationPolicyCurrentTab; ActionSpecifiesNavigationPolicy(action, &navigation_policy); // Give the delegate a chance to change the navigation policy. const WebDataSourceImpl* ds = webframe_->GetProvisionalDataSourceImpl(); if (ds) { GURL url = ds->request().url(); if (url.SchemeIs(webkit_glue::kBackForwardNavigationScheme)) { HandleBackForwardNavigation(url); navigation_policy = WebKit::WebNavigationPolicyIgnore; } else { bool is_redirect = ds->HasRedirectChain(); WebNavigationType webnav_type = WebDataSourceImpl::NavigationTypeToWebNavigationType(action.type()); navigation_policy = d->PolicyForNavigationAction( wv, webframe_, ds->request(), webnav_type, navigation_policy, is_redirect); } } if (navigation_policy == WebKit::WebNavigationPolicyCurrentTab) { policy_action = PolicyUse; } else if (navigation_policy == WebKit::WebNavigationPolicyDownload) { policy_action = PolicyDownload; } else { if (navigation_policy != WebKit::WebNavigationPolicyIgnore) { GURL referrer = webkit_glue::StringToGURL( request.httpHeaderField("Referer")); d->OpenURL(webframe_->GetWebViewImpl(), webkit_glue::KURLToGURL(request.url()), referrer, navigation_policy); } policy_action = PolicyIgnore; } } (webframe_->frame()->loader()->*function)(policy_action); } void WebFrameLoaderClient::cancelPolicyCheck() { // FIXME } void WebFrameLoaderClient::dispatchUnableToImplementPolicy(const ResourceError&) { // FIXME } void WebFrameLoaderClient::dispatchWillSubmitForm(FramePolicyFunction function, PassRefPtr form_ref) { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (d) { d->WillSubmitForm(webview, webframe_, webkit_glue::HTMLFormElementToWebForm(form_ref->form())); } (webframe_->frame()->loader()->*function)(PolicyUse); } void WebFrameLoaderClient::dispatchDidLoadMainResource(DocumentLoader*) { // FIXME } void WebFrameLoaderClient::revertToProvisionalState(DocumentLoader*) { has_representation_ = true; } void WebFrameLoaderClient::setMainDocumentError(DocumentLoader*, const ResourceError& error) { if (plugin_widget_.get()) { if (sent_initial_response_to_plugin_) { plugin_widget_->didFailLoading(error); sent_initial_response_to_plugin_ = false; } plugin_widget_ = NULL; } } void WebFrameLoaderClient::postProgressStartedNotification() { if (hasWebView()) { WebViewImpl* web_view = webframe_->GetWebViewImpl(); WebViewDelegate* d = web_view->delegate(); if (d) d->DidStartLoading(web_view); } } void WebFrameLoaderClient::postProgressEstimateChangedNotification() { // FIXME } void WebFrameLoaderClient::postProgressFinishedNotification() { // TODO(ericroman): why might webframe_->webview_impl be null? // http://b/1234461 if (hasWebView()) { WebViewImpl* web_view = webframe_->GetWebViewImpl(); WebViewDelegate* d = web_view->delegate(); if (d) d->DidStopLoading(web_view); } } void WebFrameLoaderClient::setMainFrameDocumentReady(bool ready) { // FIXME } // Creates a new connection and begins downloading from that (contrast this // with |download|). void WebFrameLoaderClient::startDownload(const ResourceRequest& request) { WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) { const GURL url(webkit_glue::KURLToGURL(request.url())); const GURL referrer(webkit_glue::StringToStdString(request.httpReferrer())); d->DownloadUrl(url, referrer); } } void WebFrameLoaderClient::willChangeTitle(DocumentLoader*) { // FIXME } void WebFrameLoaderClient::didChangeTitle(DocumentLoader*) { // FIXME } // Called whenever data is received. void WebFrameLoaderClient::committedLoad(DocumentLoader* loader, const char* data, int length) { if (!plugin_widget_.get()) { WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) d->DidReceiveDocumentData(webframe_, data, length); } // If we are sending data to WebCore::MediaDocument, we should stop here // and cancel the request. if (webframe_->frame()->document() && webframe_->frame()->document()->isMediaDocument()) { loader->cancelMainResourceLoad( pluginWillHandleLoadError(loader->response())); } // The plugin widget could have been created in the webframe_->DidReceiveData // function. if (plugin_widget_.get()) { if (!sent_initial_response_to_plugin_) { sent_initial_response_to_plugin_ = true; plugin_widget_->didReceiveResponse( webframe_->frame()->loader()->activeDocumentLoader()->response()); } plugin_widget_->didReceiveData(data, length); } } void WebFrameLoaderClient::finishedLoading(DocumentLoader* dl) { if (plugin_widget_.get()) { plugin_widget_->didFinishLoading(); plugin_widget_ = NULL; sent_initial_response_to_plugin_ = false; } else { // This is necessary to create an empty document. See bug 634004. // However, we only want to do this if makeRepresentation has been called, to // match the behavior on the Mac. if (has_representation_) dl->frameLoader()->setEncoding("", false); } } void WebFrameLoaderClient::updateGlobalHistory() { } void WebFrameLoaderClient::updateGlobalHistoryRedirectLinks() { } bool WebFrameLoaderClient::shouldGoToHistoryItem(HistoryItem*) const { // FIXME return true; } ResourceError WebFrameLoaderClient::blockedError(const WebCore::ResourceRequest&) { // FIXME return ResourceError(); } ResourceError WebFrameLoaderClient::cancelledError( const ResourceRequest& request) { return ResourceError(net::kErrorDomain, net::ERR_ABORTED, request.url().string(), String()); } ResourceError WebFrameLoaderClient::cannotShowURLError(const ResourceRequest&) { // FIXME return ResourceError(); } ResourceError WebFrameLoaderClient::interruptForPolicyChangeError( const ResourceRequest& request) { return ResourceError(kInternalErrorDomain, ERR_POLICY_CHANGE, request.url().string(), String()); } ResourceError WebFrameLoaderClient::cannotShowMIMETypeError(const ResourceResponse&) { // FIXME return ResourceError(); } ResourceError WebFrameLoaderClient::fileDoesNotExistError(const ResourceResponse&) { // FIXME return ResourceError(); } ResourceError WebFrameLoaderClient::pluginWillHandleLoadError(const WebCore::ResourceResponse&) { // FIXME return ResourceError(); } bool WebFrameLoaderClient::shouldFallBack(const ResourceError& error) { // This method is called when we fail to load the URL for an tag // that has fallback content (child elements) and is being loaded as a frame. // The error parameter indicates the reason for the load failure. // We should let the fallback content load only if this wasn't a cancelled // request. // Note: The mac version also has a case for "WebKitErrorPluginWillHandleLoad" return error.errorCode() != net::ERR_ABORTED; } bool WebFrameLoaderClient::canHandleRequest(const ResourceRequest&) const { // FIXME: this appears to be used only by the context menu code to determine // if "open" should be displayed in the menu when clicking on a link. return true; } bool WebFrameLoaderClient::canShowMIMEType(const String& mime_type) const { // This method is called to determine if the media type can be shown // "internally" (i.e. inside the browser) regardless of whether or not the // browser or a plugin is doing the rendering. // mime_type strings are supposed to be ASCII, but if they are not for some // reason, then it just means that the mime type will fail all of these "is // supported" checks and go down the path of an unhandled mime type. if (net::IsSupportedMimeType( webkit_glue::CStringToStdString(mime_type.latin1()))) return true; // If Chrome is started with the --disable-plugins switch, pluginData is null. WebCore::PluginData* plugin_data = webframe_->frame()->page()->pluginData(); // See if the type is handled by an installed plugin, if so, we can show it. // TODO(beng): (http://b/1085524) This is the place to stick a preference to // disable full page plugins (optionally for certain types!) return !mime_type.isEmpty() && plugin_data && plugin_data->supportsMimeType(mime_type); } bool WebFrameLoaderClient::representationExistsForURLScheme(const String& URLScheme) const { // FIXME return false; } String WebFrameLoaderClient::generatedMIMETypeForURLScheme(const String& URLScheme) const { // This appears to generate MIME types for protocol handlers that are handled // internally. The only place I can find in the WebKit code that uses this // function is WebView::registerViewClass, where it is used as part of the // process by which custom view classes for certain document representations // are registered. String mimetype("x-apple-web-kit/"); mimetype.append(URLScheme.lower()); return mimetype; } void WebFrameLoaderClient::frameLoadCompleted() { // FIXME: the mac port also conditionally calls setDrawsBackground:YES on // it's ScrollView here. // This comment from the Mac port: // Note: Can be called multiple times. // Even if already complete, we might have set a previous item on a frame that // didn't do any data loading on the past transaction. Make sure to clear these out. // FIXME: setPreviousHistoryItem() no longer exists. http://crbug.com/8566 // webframe_->frame()->loader()->setPreviousHistoryItem(0); } void WebFrameLoaderClient::saveViewStateToItem(HistoryItem*) { // FIXME } void WebFrameLoaderClient::restoreViewState() { // FIXME: probably scrolls to last position when you go back or forward } void WebFrameLoaderClient::provisionalLoadStarted() { // FIXME: On mac, this does various caching stuff } void WebFrameLoaderClient::didFinishLoad() { WebPluginDelegate* plg_delegate = webframe_->plugin_delegate(); if (plg_delegate) plg_delegate->DidFinishLoadWithReason(NPRES_DONE); } void WebFrameLoaderClient::prepareForDataSourceReplacement() { // FIXME } PassRefPtr WebFrameLoaderClient::createDocumentLoader( const ResourceRequest& request, const SubstituteData& data) { RefPtr ds = WebDataSourceImpl::Create(request, data); WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) d->DidCreateDataSource(webframe_, ds.get()); return ds.release(); } void WebFrameLoaderClient::setTitle(const String& title, const KURL& url) { // FIXME: monitor for changes in WebFrameLoaderClient.mm // FIXME: Set the title of the current history item. HistoryItemImpl's setter // will notify its clients (e.g. the history database) that the title // has changed. // // e.g.: // WebHistoryItem* item = // webframe_->GetWebViewImpl()->GetBackForwardList()->GetCurrentItem(); // WebHistoryItemImpl* item_impl = static_cast(item); // // item_impl->SetTitle(webkit_glue::StringToStdWString(title)); } String WebFrameLoaderClient::userAgent(const KURL& url) { return webkit_glue::StdStringToString( webkit_glue::GetUserAgent(webkit_glue::KURLToGURL(url))); } void WebFrameLoaderClient::savePlatformDataToCachedFrame(WebCore::CachedFrame*) { NOTREACHED() << "Page cache should be disabled"; } void WebFrameLoaderClient::transitionToCommittedFromCachedFrame(WebCore::CachedFrame*) { ASSERT_NOT_REACHED(); } // Called when the FrameLoader goes into a state in which a new page load // will occur. void WebFrameLoaderClient::transitionToCommittedForNewPage() { makeDocumentView(); } bool WebFrameLoaderClient::canCachePage() const { // Since we manage the cache, always report this page as non-cacheable to // FrameLoader. return false; } // Downloading is handled in the browser process, not WebKit. If we get to this // point, our download detection code in the ResourceDispatcherHost is broken! void WebFrameLoaderClient::download(ResourceHandle* handle, const ResourceRequest& request, const ResourceRequest& initialRequest, const ResourceResponse& response) { NOTREACHED(); } PassRefPtr WebFrameLoaderClient::createFrame( const KURL& url, const String& name, HTMLFrameOwnerElement* owner_element, const String& referrer, bool allows_scrolling, int margin_width, int margin_height) { FrameLoadRequest frame_request(ResourceRequest(url, referrer), name); return webframe_->CreateChildFrame(frame_request, owner_element); } // Utility function to convert a vector to an array of char*'s. // Caller is responsible to free memory with DeleteToArray(). static char** ToArray(const Vector &vector) { char **rv = new char *[vector.size()+1]; unsigned int index = 0; for (index = 0; index < vector.size(); ++index) { WebCore::CString src = vector[index].utf8(); rv[index] = new char[src.length() + 1]; base::strlcpy(rv[index], src.data(), src.length() + 1); rv[index][src.length()] = '\0'; } rv[index] = 0; return rv; } static void DeleteToArray(char** arr) { char **ptr = arr; while (*ptr != 0) { delete [] *ptr; ++ptr; } delete [] arr; } PassRefPtr WebFrameLoaderClient::createPlugin(const IntSize& size, // TODO(erikkay): how do we use this? HTMLPlugInElement* element, const KURL&url, const Vector& param_names, const Vector& param_values, const String& mime_type, bool load_manually) { WebViewImpl* webview = webframe_->GetWebViewImpl(); WebViewDelegate* d = webview->delegate(); if (!d) return NULL; GURL gurl = webkit_glue::KURLToGURL(url); std::string my_mime_type = webkit_glue::CStringToStdString(mime_type.latin1()); StringToLowerASCII(&my_mime_type); // Get the classid and version from attributes of the object. std::string combined_clsid; #if defined(OS_WIN) std::string clsid, version; if (activex_shim::IsMimeTypeActiveX(my_mime_type)) { GURL url = webframe_->url(); for (unsigned int i = 0; i < param_names.size(); i++) { String lowercase_param_name = param_names[i].lower(); if (lowercase_param_name == "classid") { activex_shim::GetClsidFromClassidAttribute( webkit_glue::CStringToStdString(param_values[i].latin1()), &clsid); } else if (lowercase_param_name == "codebase") { version = activex_shim::GetVersionFromCodebaseAttribute( webkit_glue::CStringToStdString(param_values[i].latin1())); } } // Attempt to map this clsid to a known NPAPI mime type if possible, failing // which we attempt to load the activex shim for the clsid. if (!activex_shim::GetMimeTypeForClsid(clsid, &my_mime_type)) { // We need to pass the combined clsid + version to PluginsList, so that it // would detect if the requested version is installed. If not, it needs // to use the default plugin to update the control. if (!version.empty()) combined_clsid = clsid + "#" + version; else combined_clsid = clsid; } } #endif #if defined(OS_POSIX) // WebCore asks us to make a plugin even if we don't have a // registered handler, with a comment saying it's so we can display // the broken plugin icon. In Chromium, we normally register a // fallback plugin handler that allows you to install a missing // plugin. Since we don't yet have a default plugin handler, we // need to return NULL here rather than going through all the // plugin-creation IPCs only to discover we don't have a plugin // registered, which causes a crash. // TODO(evanm): remove me once we have a default plugin. if (objectContentType(url, mime_type) != ObjectContentNetscapePlugin) return NULL; #endif std::string actual_mime_type; WebPluginDelegate* plugin_delegate = d->CreatePluginDelegate(webframe_->GetWebViewImpl(), gurl, my_mime_type, combined_clsid, &actual_mime_type); if (!plugin_delegate) return NULL; if (!actual_mime_type.empty()) my_mime_type = actual_mime_type; DCHECK(param_names.size() == param_values.size()); char **argn = ToArray(param_names); char **argv = ToArray(param_values); int argc = static_cast(param_names.size()); RefPtr result = WebPluginImpl::Create(gurl, argn, argv, argc, element, webframe_, plugin_delegate, load_manually, my_mime_type); DeleteToArray(argn); DeleteToArray(argv); return result; } // This method gets called when a plugin is put in place of html content // (e.g., acrobat reader). void WebFrameLoaderClient::redirectDataToPlugin(Widget* pluginWidget) { plugin_widget_ = static_cast(pluginWidget); DCHECK(plugin_widget_.get()); } PassRefPtr WebFrameLoaderClient::createJavaAppletWidget( const IntSize& size, HTMLAppletElement* element, const KURL& /* base_url */, const Vector& param_names, const Vector& param_values) { return createPlugin(size, element, KURL(), param_names, param_values, "application/x-java-applet", false); } ObjectContentType WebFrameLoaderClient::objectContentType( const KURL& url, const String& explicit_mime_type) { // This code is based on Apple's implementation from // WebCoreSupport/WebFrameBridge.mm. String mime_type = explicit_mime_type; if (mime_type.isEmpty()) { // Try to guess the MIME type based off the extension. String filename = url.lastPathComponent(); int extension_pos = filename.reverseFind('.'); if (extension_pos >= 0) mime_type = MIMETypeRegistry::getMIMETypeForPath(url.path()); if (mime_type.isEmpty()) return ObjectContentFrame; } if (MIMETypeRegistry::isSupportedImageMIMEType(mime_type)) return ObjectContentImage; // If Chrome is started with the --disable-plugins switch, pluginData is null. PluginData* plugin_data = webframe_->frame()->page()->pluginData(); if (plugin_data && plugin_data->supportsMimeType(mime_type)) return ObjectContentNetscapePlugin; if (MIMETypeRegistry::isSupportedNonImageMIMEType(mime_type)) return ObjectContentFrame; return ObjectContentNone; } String WebFrameLoaderClient::overrideMediaType() const { // FIXME String rv; return rv; } bool WebFrameLoaderClient::ActionSpecifiesNavigationPolicy( const WebCore::NavigationAction& action, WebNavigationPolicy* policy) { if ((action.type() != NavigationTypeLinkClicked) || !action.event()->isMouseEvent()) return false; const MouseEvent* event = static_cast(action.event()); #if defined(OS_WIN) || defined(OS_LINUX) const bool new_tab_modifier = (event->button() == 1) || event->ctrlKey(); #elif defined(OS_MACOSX) const bool new_tab_modifier = (event->button() == 1) || event->metaKey(); #endif const bool shift = event->shiftKey(); const bool alt = event->altKey(); if (!new_tab_modifier && !shift && !alt) return false; DCHECK(policy); if (new_tab_modifier) { if (shift) { *policy = WebKit::WebNavigationPolicyNewForegroundTab; } else { *policy = WebKit::WebNavigationPolicyNewBackgroundTab; } } else { if (shift) { *policy = WebKit::WebNavigationPolicyNewWindow; } else { *policy = WebKit::WebNavigationPolicyDownload; } } return true; } NavigationGesture WebFrameLoaderClient::NavigationGestureForLastLoad() { // TODO(timsteele): isProcessingUserGesture() returns too many false positives // (see bug 1051891) to trust it and assign NavigationGestureUser, so // for now we assign Unknown in those cases and Auto otherwise. // (Issue 874811 known false negative as well). return webframe_->frame()->loader()->isProcessingUserGesture() ? NavigationGestureUnknown : NavigationGestureAuto; } void WebFrameLoaderClient::HandleBackForwardNavigation(const GURL& url) { DCHECK(url.SchemeIs(webkit_glue::kBackForwardNavigationScheme)); std::string offset_str = url.ExtractFileName(); int offset; if (!StringToInt(offset_str, &offset)) return; WebViewDelegate* d = webframe_->GetWebViewImpl()->delegate(); if (d) d->NavigateBackForwardSoon(offset); }