/* * Copyright (C) 2009 Google Inc. All rights reserved. * Copyright (C) 2011 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "FrameLoaderClientImpl.h" #include "BackForwardListChromium.h" #include "Chrome.h" #include "Document.h" #include "DocumentLoader.h" #include "FormState.h" #include "FrameLoader.h" #include "FrameLoadRequest.h" #include "FrameNetworkingContextImpl.h" #include "FrameView.h" #include "HTTPParsers.h" #include "HistoryItem.h" #include "HitTestResult.h" #include "HTMLAppletElement.h" #include "HTMLFormElement.h" // needed by FormState.h #include "HTMLNames.h" #include "MIMETypeRegistry.h" #include "MouseEvent.h" #include "Page.h" #include "PlatformString.h" #include "PluginData.h" #include "PluginDataChromium.h" #include "ProgressTracker.h" #include "Settings.h" #include "StringExtras.h" #include "WebDataSourceImpl.h" #include "WebDevToolsAgentPrivate.h" #include "WebFormElement.h" #include "WebFrameClient.h" #include "WebFrameImpl.h" #include "WebKit.h" #include "WebKitClient.h" #include "WebMimeRegistry.h" #include "WebNode.h" #include "WebPlugin.h" #include "WebPluginContainerImpl.h" #include "WebPluginLoadObserver.h" #include "WebPluginParams.h" #include "WebSecurityOrigin.h" #include "WebURL.h" #include "WebURLError.h" #include "WebVector.h" #include "WebViewClient.h" #include "WebViewImpl.h" #include "WindowFeatures.h" #include "WrappedResourceRequest.h" #include "WrappedResourceResponse.h" #include using namespace WebCore; namespace WebKit { // Domain for internal error codes. static const char internalErrorDomain[] = "WebKit"; // An internal error code. Used to note a policy change error resulting from // dispatchDecidePolicyForMIMEType not passing the PolicyUse option. enum { PolicyChangeError = -10000, }; FrameLoaderClientImpl::FrameLoaderClientImpl(WebFrameImpl* frame) : m_webFrame(frame) , m_hasRepresentation(false) , m_sentInitialResponseToPlugin(false) , m_nextNavigationPolicy(WebNavigationPolicyIgnore) { } FrameLoaderClientImpl::~FrameLoaderClientImpl() { } void FrameLoaderClientImpl::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. m_webFrame->closing(); m_webFrame->deref(); } void FrameLoaderClientImpl::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld*) { if (m_webFrame->client()) m_webFrame->client()->didClearWindowObject(m_webFrame); WebViewImpl* webview = m_webFrame->viewImpl(); if (webview->devToolsAgentPrivate()) webview->devToolsAgentPrivate()->didClearWindowObject(m_webFrame); } void FrameLoaderClientImpl::documentElementAvailable() { if (m_webFrame->client()) m_webFrame->client()->didCreateDocumentElement(m_webFrame); } void FrameLoaderClientImpl::didCreateScriptContextForFrame() { if (m_webFrame->client()) m_webFrame->client()->didCreateScriptContext(m_webFrame); } void FrameLoaderClientImpl::didDestroyScriptContextForFrame() { if (m_webFrame->client()) m_webFrame->client()->didDestroyScriptContext(m_webFrame); } void FrameLoaderClientImpl::didCreateIsolatedScriptContext() { if (m_webFrame->client()) m_webFrame->client()->didCreateIsolatedScriptContext(m_webFrame); } bool FrameLoaderClientImpl::allowScriptExtension(const String& extensionName, int extensionGroup) { if (m_webFrame->client()) return m_webFrame->client()->allowScriptExtension(m_webFrame, extensionName, extensionGroup); return false; } void FrameLoaderClientImpl::didPerformFirstNavigation() const { } void FrameLoaderClientImpl::registerForIconNotification(bool) { } void FrameLoaderClientImpl::didChangeScrollOffset() { if (m_webFrame->client()) m_webFrame->client()->didChangeScrollOffset(m_webFrame); } bool FrameLoaderClientImpl::allowJavaScript(bool enabledPerSettings) { if (m_webFrame->client()) return m_webFrame->client()->allowScript(m_webFrame, enabledPerSettings); return enabledPerSettings; } bool FrameLoaderClientImpl::allowPlugins(bool enabledPerSettings) { if (m_webFrame->client()) return m_webFrame->client()->allowPlugins(m_webFrame, enabledPerSettings); return enabledPerSettings; } bool FrameLoaderClientImpl::allowImages(bool enabledPerSettings) { if (m_webFrame->client()) return m_webFrame->client()->allowImages(m_webFrame, enabledPerSettings); return enabledPerSettings; } void FrameLoaderClientImpl::didNotAllowScript() { if (m_webFrame->client()) m_webFrame->client()->didNotAllowScript(m_webFrame); } void FrameLoaderClientImpl::didNotAllowPlugins() { if (m_webFrame->client()) m_webFrame->client()->didNotAllowPlugins(m_webFrame); } bool FrameLoaderClientImpl::hasWebView() const { return m_webFrame->viewImpl(); } bool FrameLoaderClientImpl::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 m_webFrame->viewImpl(); } void FrameLoaderClientImpl::makeDocumentView() { m_webFrame->createFrameView(); } void FrameLoaderClientImpl::makeRepresentation(DocumentLoader*) { m_hasRepresentation = true; } void FrameLoaderClientImpl::forceLayout() { // FIXME } void FrameLoaderClientImpl::forceLayoutForNonHTML() { // FIXME } void FrameLoaderClientImpl::setCopiesOnScroll() { // FIXME } void FrameLoaderClientImpl::detachedFromParent2() { // Nothing to do here. } void FrameLoaderClientImpl::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. m_webFrame->frame()->script()->proxy()->clearForClose(); // Alert the client that the frame is being detached. This is the last // chance we have to communicate with the client. if (m_webFrame->client()) m_webFrame->client()->frameDetached(m_webFrame); // Stop communicating with the WebFrameClient at this point since we are no // longer associated with the Page. m_webFrame->setClient(0); } // 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 FrameLoaderClientImpl::assignIdentifierToInitialRequest( unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request) { if (m_webFrame->client()) { WrappedResourceRequest webreq(request); m_webFrame->client()->assignIdentifierToRequest( m_webFrame, identifier, webreq); } } // If the request being loaded by |loader| is a frame, update the ResourceType. // 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 void setTargetTypeFromLoader(ResourceRequest& request, DocumentLoader* loader) { if (loader == loader->frameLoader()->provisionalDocumentLoader()) { ResourceRequest::TargetType type; if (loader->frameLoader()->isLoadingMainFrame()) type = ResourceRequest::TargetIsMainFrame; else type = ResourceRequest::TargetIsSubframe; request.setTargetType(type); } } void FrameLoaderClientImpl::dispatchWillSendRequest( DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse) { 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. setTargetTypeFromLoader(request, loader); // Avoid repeating a form submission when navigating back or forward. if (loader == loader->frameLoader()->provisionalDocumentLoader() && request.httpMethod() == "POST" && isBackForwardLoadType(loader->frameLoader()->loadType())) request.setCachePolicy(ReturnCacheDataDontLoad); } // 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(ParsedURLString, "about:blank")); if (request.firstPartyForCookies().isEmpty()) request.setFirstPartyForCookies(KURL(ParsedURLString, "about:blank")); // Give the WebFrameClient a crack at the request. if (m_webFrame->client()) { WrappedResourceRequest webreq(request); WrappedResourceResponse webresp(redirectResponse); m_webFrame->client()->willSendRequest( m_webFrame, identifier, webreq, webresp); } } bool FrameLoaderClientImpl::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 FrameLoaderClientImpl::dispatchDidReceiveAuthenticationChallenge( DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) { // FIXME } void FrameLoaderClientImpl::dispatchDidCancelAuthenticationChallenge( DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) { // FIXME } void FrameLoaderClientImpl::dispatchDidReceiveResponse(DocumentLoader* loader, unsigned long identifier, const ResourceResponse& response) { if (m_webFrame->client()) { WrappedResourceResponse webresp(response); m_webFrame->client()->didReceiveResponse(m_webFrame, identifier, webresp); } } void FrameLoaderClientImpl::dispatchDidReceiveContentLength( DocumentLoader* loader, unsigned long identifier, int dataLength) { } // Called when a particular resource load completes void FrameLoaderClientImpl::dispatchDidFinishLoading(DocumentLoader* loader, unsigned long identifier) { if (m_webFrame->client()) m_webFrame->client()->didFinishResourceLoad(m_webFrame, identifier); } void FrameLoaderClientImpl::dispatchDidFailLoading(DocumentLoader* loader, unsigned long identifier, const ResourceError& error) { if (m_webFrame->client()) m_webFrame->client()->didFailResourceLoad(m_webFrame, identifier, error); } void FrameLoaderClientImpl::dispatchDidFinishDocumentLoad() { // A frame may be reused. This call ensures we don't hold on to our password // listeners and their associated HTMLInputElements. m_webFrame->clearPasswordListeners(); if (m_webFrame->client()) m_webFrame->client()->didFinishDocumentLoad(m_webFrame); } bool FrameLoaderClientImpl::dispatchDidLoadResourceFromMemoryCache( DocumentLoader* loader, const ResourceRequest& request, const ResourceResponse& response, int length) { if (m_webFrame->client()) { WrappedResourceRequest webreq(request); WrappedResourceResponse webresp(response); m_webFrame->client()->didLoadResourceFromMemoryCache( m_webFrame, webreq, webresp); } return false; // Do not suppress remaining notifications } void FrameLoaderClientImpl::dispatchDidHandleOnloadEvents() { if (m_webFrame->client()) m_webFrame->client()->didHandleOnloadEvents(m_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 FrameLoaderClientImpl::dispatchDidReceiveServerRedirectForProvisionalLoad() { WebDataSourceImpl* ds = m_webFrame->provisionalDataSourceImpl(); if (!ds) { // Got a server redirect when there is no provisional DS! ASSERT_NOT_REACHED(); 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. ASSERT(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()); if (m_webFrame->client()) m_webFrame->client()->didReceiveServerRedirectForProvisionalLoad(m_webFrame); } // Called on both success and failure of a client redirect. void FrameLoaderClientImpl::dispatchDidCancelClientRedirect() { // No longer expecting a client redirect. if (m_webFrame->client()) { m_expectedClientRedirectSrc = KURL(); m_expectedClientRedirectDest = KURL(); m_webFrame->client()->didCancelClientRedirect(m_webFrame); } // No need to clear the redirect chain, since that data source has already // been deleted by the time this function is called. } void FrameLoaderClientImpl::dispatchWillPerformClientRedirect( const KURL& url, double interval, double fireDate) { // 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. m_expectedClientRedirectSrc = m_webFrame->url(); m_expectedClientRedirectDest = url; // FIXME: 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 (m_expectedClientRedirectDest.isLocalFile() && m_expectedClientRedirectSrc.protocolInHTTPFamily()) { m_expectedClientRedirectSrc = KURL(); m_expectedClientRedirectDest = KURL(); return; } if (m_webFrame->client()) { m_webFrame->client()->willPerformClientRedirect( m_webFrame, m_expectedClientRedirectSrc, m_expectedClientRedirectDest, static_cast(interval), static_cast(fireDate)); } } void FrameLoaderClientImpl::dispatchDidNavigateWithinPage() { // Anchor fragment navigations are not normal loads, so we need to synthesize // some events for our delegate. WebViewImpl* webView = m_webFrame->viewImpl(); // Flag of whether frame loader is completed. Generate didStartLoading and // didStopLoading only when loader is completed so that we don't fire // them for fragment redirection that happens in window.onload handler. // See https://bugs.webkit.org/show_bug.cgi?id=31838 bool loaderCompleted = !webView->page()->mainFrame()->loader()->activeDocumentLoader()->isLoadingInAPISense(); // Generate didStartLoading if loader is completed. if (webView->client() && loaderCompleted) webView->client()->didStartLoading(); // We need to classify some hash changes as client redirects. // FIXME: It seems wrong that the currentItem can sometimes be null. HistoryItem* currentItem = m_webFrame->frame()->loader()->history()->currentItem(); bool isHashChange = !currentItem || !currentItem->stateObject(); WebDataSourceImpl* ds = m_webFrame->dataSourceImpl(); ASSERT(ds); // Should not be null when navigating to a reference fragment! if (ds) { KURL url = ds->request().url(); KURL chainEnd; if (ds->hasRedirectChain()) { chainEnd = ds->endOfRedirectChain(); ds->clearRedirectChain(); } if (isHashChange) { // Figure out if this location change is because of a JS-initiated // client redirect (e.g onload/setTimeout document.location.href=). // FIXME: (b/1085325, b/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 wasClientRedirect = (url == m_expectedClientRedirectDest && chainEnd == m_expectedClientRedirectSrc) || !m_webFrame->isProcessingUserGesture(); if (wasClientRedirect) { if (m_webFrame->client()) m_webFrame->client()->didCompleteClientRedirect(m_webFrame, chainEnd); ds->appendRedirect(chainEnd); // Make sure we clear the expected redirect since we just effectively // completed it. m_expectedClientRedirectSrc = KURL(); m_expectedClientRedirectDest = KURL(); } } // 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 isNewNavigation; webView->didCommitLoad(&isNewNavigation); if (m_webFrame->client()) m_webFrame->client()->didNavigateWithinPage(m_webFrame, isNewNavigation); // Generate didStopLoading if loader is completed. if (webView->client() && loaderCompleted) webView->client()->didStopLoading(); } void FrameLoaderClientImpl::dispatchDidChangeLocationWithinPage() { if (m_webFrame) m_webFrame->client()->didChangeLocationWithinPage(m_webFrame); } void FrameLoaderClientImpl::dispatchDidPushStateWithinPage() { dispatchDidNavigateWithinPage(); } void FrameLoaderClientImpl::dispatchDidReplaceStateWithinPage() { dispatchDidNavigateWithinPage(); } void FrameLoaderClientImpl::dispatchDidPopStateWithinPage() { // Ignored since dispatchDidNavigateWithinPage was already called. } void FrameLoaderClientImpl::dispatchWillClose() { if (m_webFrame->client()) m_webFrame->client()->willClose(m_webFrame); } void FrameLoaderClientImpl::dispatchDidReceiveIcon() { // The icon database is disabled, so this should never be called. ASSERT_NOT_REACHED(); } void FrameLoaderClientImpl::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 = m_webFrame->provisionalDataSourceImpl(); if (!ds) { ASSERT_NOT_REACHED(); return; } KURL url = ds->request().url(); // Since the provisional load just started, we should have not gotten // any redirects yet. ASSERT(!ds->hasRedirectChain()); // 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 completingClientRedirect = false; if (m_expectedClientRedirectSrc.isValid()) { // m_expectedClientRedirectDest could be something like // "javascript:history.go(-1)" thus we need to exclude url starts with // "javascript:". See bug: 1080873 if (m_expectedClientRedirectDest.protocolIs("javascript") || m_expectedClientRedirectDest == url) { ds->appendRedirect(m_expectedClientRedirectSrc); completingClientRedirect = true; } else { // Any pending redirect is no longer in progress. This can happen // if the navigation was canceled with PolicyIgnore, or if the // redirect was scheduled on the wrong frame (e.g., due to a form // submission targeted to _blank, as in http://webkit.org/b/44079). m_expectedClientRedirectSrc = KURL(); m_expectedClientRedirectDest = KURL(); } } ds->appendRedirect(url); if (m_webFrame->client()) { // Whatever information didCompleteClientRedirect contains should only // be considered relevant until the next provisional load has started. // So we first tell the client that the load started, and then tell it // about the client redirect the load is responsible for completing. m_webFrame->client()->didStartProvisionalLoad(m_webFrame); if (completingClientRedirect) { m_webFrame->client()->didCompleteClientRedirect( m_webFrame, m_expectedClientRedirectSrc); } } } void FrameLoaderClientImpl::dispatchDidReceiveTitle(const StringWithDirection& title) { if (m_webFrame->client()) m_webFrame->client()->didReceiveTitle(m_webFrame, title.string(), title.direction() == LTR ? WebTextDirectionLeftToRight : WebTextDirectionRightToLeft); } void FrameLoaderClientImpl::dispatchDidChangeIcons() { if (m_webFrame->client()) m_webFrame->client()->didChangeIcons(m_webFrame); } void FrameLoaderClientImpl::dispatchDidCommitLoad() { WebViewImpl* webview = m_webFrame->viewImpl(); bool isNewNavigation; webview->didCommitLoad(&isNewNavigation); if (m_webFrame->client()) m_webFrame->client()->didCommitProvisionalLoad(m_webFrame, isNewNavigation); } void FrameLoaderClientImpl::dispatchDidFailProvisionalLoad( const ResourceError& error) { // If a policy change occured, then we do not want to inform the plugin // delegate. See http://b/907789 for details. FIXME: This means the // plugin won't receive NPP_URLNotify, which seems like it could result in // a memory leak in the plugin!! if (error.domain() == internalErrorDomain && error.errorCode() == PolicyChangeError) { m_webFrame->didFail(cancelledError(error.failingURL()), true); return; } OwnPtr observer = pluginLoadObserver(); m_webFrame->didFail(error, true); if (observer) observer->didFailLoading(error); } void FrameLoaderClientImpl::dispatchDidFailLoad(const ResourceError& error) { OwnPtr observer = pluginLoadObserver(); m_webFrame->didFail(error, false); if (observer) observer->didFailLoading(error); // 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 FrameLoaderClientImpl::dispatchDidFinishLoad() { OwnPtr observer = pluginLoadObserver(); if (m_webFrame->client()) m_webFrame->client()->didFinishLoad(m_webFrame); if (observer) observer->didFinishLoading(); // 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 FrameLoaderClientImpl::dispatchDidFirstLayout() { if (m_webFrame->client()) m_webFrame->client()->didFirstLayout(m_webFrame); } void FrameLoaderClientImpl::dispatchDidFirstVisuallyNonEmptyLayout() { if (m_webFrame->client()) m_webFrame->client()->didFirstVisuallyNonEmptyLayout(m_webFrame); } Frame* FrameLoaderClientImpl::dispatchCreatePage(const NavigationAction& action) { struct WindowFeatures features; Page* newPage = m_webFrame->frame()->page()->chrome()->createWindow( m_webFrame->frame(), FrameLoadRequest(m_webFrame->frame()->document()->securityOrigin()), features, action); // Make sure that we have a valid disposition. This should have been set in // the preceeding call to dispatchDecidePolicyForNewWindowAction. ASSERT(m_nextNavigationPolicy != WebNavigationPolicyIgnore); WebNavigationPolicy policy = m_nextNavigationPolicy; m_nextNavigationPolicy = WebNavigationPolicyIgnore; // createWindow can return null (e.g., popup blocker denies the window). if (!newPage) return 0; WebViewImpl::fromPage(newPage)->setInitialNavigationPolicy(policy); return newPage->mainFrame(); } void FrameLoaderClientImpl::dispatchShow() { WebViewImpl* webView = m_webFrame->viewImpl(); if (webView && webView->client()) webView->client()->show(webView->initialNavigationPolicy()); } void FrameLoaderClientImpl::dispatchDecidePolicyForResponse( FramePolicyFunction function, const ResourceResponse& response, const ResourceRequest&) { PolicyAction action; int statusCode = response.httpStatusCode(); if (statusCode == 204 || statusCode == 205) { // The server does not want us to replace the page contents. action = PolicyIgnore; } else if (WebCore::contentDispositionType(response.httpHeaderField("Content-Disposition")) == WebCore::ContentDispositionAttachment) { // 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(response.mimeType())) { // Make sure that we can actually handle this type internally. action = PolicyIgnore; } else { // OK, we will render this page. action = PolicyUse; } // NOTE: PolicyChangeError will be generated when action is not PolicyUse. (m_webFrame->frame()->loader()->policyChecker()->*function)(action); } void FrameLoaderClientImpl::dispatchDecidePolicyForNewWindowAction( FramePolicyFunction function, const NavigationAction& action, const ResourceRequest& request, PassRefPtr formState, const String& frameName) { WebNavigationPolicy navigationPolicy; if (!actionSpecifiesNavigationPolicy(action, &navigationPolicy)) navigationPolicy = WebNavigationPolicyNewForegroundTab; PolicyAction policyAction; if (navigationPolicy == WebNavigationPolicyDownload) policyAction = PolicyDownload; else { policyAction = 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. m_nextNavigationPolicy = navigationPolicy; } (m_webFrame->frame()->loader()->policyChecker()->*function)(policyAction); } void FrameLoaderClientImpl::dispatchDecidePolicyForNavigationAction( FramePolicyFunction function, const NavigationAction& action, const ResourceRequest& request, PassRefPtr formState) { PolicyAction policyAction = PolicyIgnore; // 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 (m_webFrame->client() && !request.url().isNull()) { WebNavigationPolicy navigationPolicy = WebNavigationPolicyCurrentTab; actionSpecifiesNavigationPolicy(action, &navigationPolicy); // Give the delegate a chance to change the navigation policy. const WebDataSourceImpl* ds = m_webFrame->provisionalDataSourceImpl(); if (ds) { KURL url = ds->request().url(); ASSERT(!url.protocolIs(backForwardNavigationScheme)); bool isRedirect = ds->hasRedirectChain(); WebNavigationType webnavType = WebDataSourceImpl::toWebNavigationType(action.type()); RefPtr node; for (const Event* event = action.event(); event; event = event->underlyingEvent()) { if (event->isMouseEvent()) { const MouseEvent* mouseEvent = static_cast(event); node = m_webFrame->frame()->eventHandler()->hitTestResultAtPoint( mouseEvent->absoluteLocation(), false).innerNonSharedNode(); break; } } WebNode originatingNode(node); navigationPolicy = m_webFrame->client()->decidePolicyForNavigation( m_webFrame, ds->request(), webnavType, originatingNode, navigationPolicy, isRedirect); } if (navigationPolicy == WebNavigationPolicyCurrentTab) policyAction = PolicyUse; else if (navigationPolicy == WebNavigationPolicyDownload) policyAction = PolicyDownload; else { if (navigationPolicy != WebNavigationPolicyIgnore) { WrappedResourceRequest webreq(request); m_webFrame->client()->loadURLExternally(m_webFrame, webreq, navigationPolicy); } policyAction = PolicyIgnore; } } (m_webFrame->frame()->loader()->policyChecker()->*function)(policyAction); } void FrameLoaderClientImpl::cancelPolicyCheck() { // FIXME } void FrameLoaderClientImpl::dispatchUnableToImplementPolicy(const ResourceError& error) { m_webFrame->client()->unableToImplementPolicyWithError(m_webFrame, error); } void FrameLoaderClientImpl::dispatchWillSendSubmitEvent(HTMLFormElement* form) { if (m_webFrame->client()) m_webFrame->client()->willSendSubmitEvent(m_webFrame, WebFormElement(form)); } void FrameLoaderClientImpl::dispatchWillSubmitForm(FramePolicyFunction function, PassRefPtr formState) { if (m_webFrame->client()) m_webFrame->client()->willSubmitForm(m_webFrame, WebFormElement(formState->form())); (m_webFrame->frame()->loader()->policyChecker()->*function)(PolicyUse); } void FrameLoaderClientImpl::dispatchDidLoadMainResource(DocumentLoader*) { // FIXME } void FrameLoaderClientImpl::revertToProvisionalState(DocumentLoader*) { m_hasRepresentation = true; } void FrameLoaderClientImpl::setMainDocumentError(DocumentLoader*, const ResourceError& error) { if (m_pluginWidget.get()) { if (m_sentInitialResponseToPlugin) { m_pluginWidget->didFailLoading(error); m_sentInitialResponseToPlugin = false; } m_pluginWidget = 0; } } void FrameLoaderClientImpl::postProgressStartedNotification() { WebViewImpl* webview = m_webFrame->viewImpl(); if (webview && webview->client()) webview->client()->didStartLoading(); } void FrameLoaderClientImpl::postProgressEstimateChangedNotification() { WebViewImpl* webview = m_webFrame->viewImpl(); if (webview && webview->client()) { webview->client()->didChangeLoadProgress( m_webFrame, m_webFrame->frame()->page()->progress()->estimatedProgress()); } } void FrameLoaderClientImpl::postProgressFinishedNotification() { // FIXME: why might the webview be null? http://b/1234461 WebViewImpl* webview = m_webFrame->viewImpl(); if (webview && webview->client()) webview->client()->didStopLoading(); } void FrameLoaderClientImpl::setMainFrameDocumentReady(bool ready) { // FIXME } // Creates a new connection and begins downloading from that (contrast this // with |download|). void FrameLoaderClientImpl::startDownload(const ResourceRequest& request) { if (m_webFrame->client()) { WrappedResourceRequest webreq(request); m_webFrame->client()->loadURLExternally( m_webFrame, webreq, WebNavigationPolicyDownload); } } void FrameLoaderClientImpl::willChangeTitle(DocumentLoader*) { // FIXME } void FrameLoaderClientImpl::didChangeTitle(DocumentLoader*) { // FIXME } // Called whenever data is received. void FrameLoaderClientImpl::committedLoad(DocumentLoader* loader, const char* data, int length) { if (!m_pluginWidget.get()) { if (m_webFrame->client()) { bool preventDefault = false; m_webFrame->client()->didReceiveDocumentData(m_webFrame, data, length, preventDefault); if (!preventDefault) m_webFrame->commitDocumentData(data, length); } } // If we are sending data to MediaDocument, we should stop here // and cancel the request. if (m_webFrame->frame()->document()->isMediaDocument()) loader->cancelMainResourceLoad(pluginWillHandleLoadError(loader->response())); // The plugin widget could have been created in the m_webFrame->DidReceiveData // function. if (m_pluginWidget.get()) { if (!m_sentInitialResponseToPlugin) { m_sentInitialResponseToPlugin = true; m_pluginWidget->didReceiveResponse( m_webFrame->frame()->loader()->activeDocumentLoader()->response()); } // It's possible that the above call removed the pointer to the plugin, so // check before calling it. if (m_pluginWidget.get()) m_pluginWidget->didReceiveData(data, length); } } void FrameLoaderClientImpl::finishedLoading(DocumentLoader* dl) { if (m_pluginWidget.get()) { m_pluginWidget->didFinishLoading(); m_pluginWidget = 0; m_sentInitialResponseToPlugin = 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 (m_hasRepresentation) dl->writer()->setEncoding("", false); } } void FrameLoaderClientImpl::updateGlobalHistory() { } void FrameLoaderClientImpl::updateGlobalHistoryRedirectLinks() { } bool FrameLoaderClientImpl::shouldGoToHistoryItem(HistoryItem* item) const { const KURL& url = item->url(); if (!url.protocolIs(backForwardNavigationScheme)) return true; // Else, we'll punt this history navigation to the embedder. It is // necessary that we intercept this here, well before the FrameLoader // has made any state changes for this history traversal. bool ok; int offset = url.lastPathComponent().toIntStrict(&ok); if (!ok) { ASSERT_NOT_REACHED(); return false; } WebViewImpl* webview = m_webFrame->viewImpl(); if (webview->client()) webview->client()->navigateBackForwardSoon(offset); return false; } bool FrameLoaderClientImpl::shouldStopLoadingForHistoryItem(HistoryItem* targetItem) const { // Don't stop loading for pseudo-back-forward URLs, since they will get // translated and then pass through again. const KURL& url = targetItem->url(); return !url.protocolIs(backForwardNavigationScheme); } void FrameLoaderClientImpl::dispatchDidAddBackForwardItem(HistoryItem*) const { } void FrameLoaderClientImpl::dispatchDidRemoveBackForwardItem(HistoryItem*) const { } void FrameLoaderClientImpl::dispatchDidChangeBackForwardIndex() const { } void FrameLoaderClientImpl::didDisplayInsecureContent() { if (m_webFrame->client()) m_webFrame->client()->didDisplayInsecureContent(m_webFrame); } void FrameLoaderClientImpl::didRunInsecureContent(SecurityOrigin* origin, const KURL& insecureURL) { if (m_webFrame->client()) m_webFrame->client()->didRunInsecureContent(m_webFrame, WebSecurityOrigin(origin), insecureURL); } ResourceError FrameLoaderClientImpl::blockedError(const ResourceRequest&) { // FIXME return ResourceError(); } ResourceError FrameLoaderClientImpl::cancelledError(const ResourceRequest& request) { if (!m_webFrame->client()) return ResourceError(); return m_webFrame->client()->cancelledError( m_webFrame, WrappedResourceRequest(request)); } ResourceError FrameLoaderClientImpl::cannotShowURLError(const ResourceRequest& request) { if (!m_webFrame->client()) return ResourceError(); return m_webFrame->client()->cannotHandleRequestError( m_webFrame, WrappedResourceRequest(request)); } ResourceError FrameLoaderClientImpl::interruptForPolicyChangeError( const ResourceRequest& request) { return ResourceError(internalErrorDomain, PolicyChangeError, request.url().string(), String()); } ResourceError FrameLoaderClientImpl::cannotShowMIMETypeError(const ResourceResponse&) { // FIXME return ResourceError(); } ResourceError FrameLoaderClientImpl::fileDoesNotExistError(const ResourceResponse&) { // FIXME return ResourceError(); } ResourceError FrameLoaderClientImpl::pluginWillHandleLoadError(const ResourceResponse&) { // FIXME return ResourceError(); } bool FrameLoaderClientImpl::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" ResourceError c = cancelledError(ResourceRequest()); return error.errorCode() != c.errorCode() || error.domain() != c.domain(); } bool FrameLoaderClientImpl::canHandleRequest(const ResourceRequest& request) const { return m_webFrame->client()->canHandleRequest( m_webFrame, WrappedResourceRequest(request)); } bool FrameLoaderClientImpl::canShowMIMETypeAsHTML(const String& MIMEType) const { notImplemented(); return false; } bool FrameLoaderClientImpl::canShowMIMEType(const String& mimeType) 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. // mimeType 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 (webKitClient()->mimeRegistry()->supportsMIMEType(mimeType) == WebMimeRegistry::IsSupported) return true; // If Chrome is started with the --disable-plugins switch, pluginData is null. PluginData* pluginData = m_webFrame->frame()->page()->pluginData(); // See if the type is handled by an installed plugin, if so, we can show it. // FIXME: (http://b/1085524) This is the place to stick a preference to // disable full page plugins (optionally for certain types!) return !mimeType.isEmpty() && pluginData && pluginData->supportsMimeType(mimeType); } bool FrameLoaderClientImpl::representationExistsForURLScheme(const String&) const { // FIXME return false; } String FrameLoaderClientImpl::generatedMIMETypeForURLScheme(const String& scheme) 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(scheme.lower()); return mimeType; } void FrameLoaderClientImpl::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 // m_webFrame->frame()->loader()->setPreviousHistoryItem(0); } void FrameLoaderClientImpl::saveViewStateToItem(HistoryItem*) { // FIXME } void FrameLoaderClientImpl::restoreViewState() { // FIXME: probably scrolls to last position when you go back or forward } void FrameLoaderClientImpl::provisionalLoadStarted() { // FIXME: On mac, this does various caching stuff } void FrameLoaderClientImpl::didFinishLoad() { OwnPtr observer = pluginLoadObserver(); if (observer) observer->didFinishLoading(); } void FrameLoaderClientImpl::prepareForDataSourceReplacement() { // FIXME } PassRefPtr FrameLoaderClientImpl::createDocumentLoader( const ResourceRequest& request, const SubstituteData& data) { RefPtr ds = WebDataSourceImpl::create(request, data); if (m_webFrame->client()) m_webFrame->client()->didCreateDataSource(m_webFrame, ds.get()); return ds.release(); } void FrameLoaderClientImpl::setTitle(const StringWithDirection& title, const KURL& url) { // FIXME: inform consumer of changes to the title. } String FrameLoaderClientImpl::userAgent(const KURL& url) { return webKitClient()->userAgent(url); } void FrameLoaderClientImpl::savePlatformDataToCachedFrame(CachedFrame*) { // The page cache should be disabled. ASSERT_NOT_REACHED(); } void FrameLoaderClientImpl::transitionToCommittedFromCachedFrame(CachedFrame*) { ASSERT_NOT_REACHED(); } // Called when the FrameLoader goes into a state in which a new page load // will occur. void FrameLoaderClientImpl::transitionToCommittedForNewPage() { makeDocumentView(); } void FrameLoaderClientImpl::didSaveToPageCache() { } void FrameLoaderClientImpl::didRestoreFromPageCache() { } void FrameLoaderClientImpl::dispatchDidBecomeFrameset(bool) { } bool FrameLoaderClientImpl::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 FrameLoaderClientImpl::download(ResourceHandle* handle, const ResourceRequest& request, const ResourceRequest& initialRequest, const ResourceResponse& response) { ASSERT_NOT_REACHED(); } PassRefPtr FrameLoaderClientImpl::createFrame( const KURL& url, const String& name, HTMLFrameOwnerElement* ownerElement, const String& referrer, bool allowsScrolling, int marginWidth, int marginHeight) { FrameLoadRequest frameRequest(m_webFrame->frame()->document()->securityOrigin(), ResourceRequest(url, referrer), name); return m_webFrame->createChildFrame(frameRequest, ownerElement); } void FrameLoaderClientImpl::didTransferChildFrameToNewDocument(Page*) { ASSERT(m_webFrame->frame()->ownerElement()); WebFrameImpl* newParent = static_cast(m_webFrame->parent()); if (!newParent || !newParent->client()) return; // Replace the client since the old client may be destroyed when the // previous page is closed. m_webFrame->setClient(newParent->client()); } void FrameLoaderClientImpl::transferLoadingResourceFromPage(unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request, Page* oldPage) { assignIdentifierToInitialRequest(identifier, loader, request); WebFrameImpl* oldWebFrame = WebFrameImpl::fromFrame(oldPage->mainFrame()); if (oldWebFrame && oldWebFrame->client()) oldWebFrame->client()->removeIdentifierForRequest(identifier); } PassRefPtr FrameLoaderClientImpl::createPlugin( const IntSize& size, // FIXME: how do we use this? HTMLPlugInElement* element, const KURL& url, const Vector& paramNames, const Vector& paramValues, const String& mimeType, bool loadManually) { if (!m_webFrame->client()) return 0; WebPluginParams params; params.url = url; params.mimeType = mimeType; params.attributeNames = paramNames; params.attributeValues = paramValues; params.loadManually = loadManually; WebPlugin* webPlugin = m_webFrame->client()->createPlugin(m_webFrame, params); if (!webPlugin) return 0; // The container takes ownership of the WebPlugin. RefPtr container = WebPluginContainerImpl::create(element, webPlugin); if (!webPlugin->initialize(container.get())) return 0; // The element might have been removed during plugin initialization! if (!element->renderer()) return 0; return container; } // This method gets called when a plugin is put in place of html content // (e.g., acrobat reader). void FrameLoaderClientImpl::redirectDataToPlugin(Widget* pluginWidget) { if (pluginWidget->isPluginContainer()) m_pluginWidget = static_cast(pluginWidget); ASSERT(m_pluginWidget.get()); } PassRefPtr FrameLoaderClientImpl::createJavaAppletWidget( const IntSize& size, HTMLAppletElement* element, const KURL& /* baseURL */, const Vector& paramNames, const Vector& paramValues) { return createPlugin(size, element, KURL(), paramNames, paramValues, "application/x-java-applet", false); } ObjectContentType FrameLoaderClientImpl::objectContentType( const KURL& url, const String& explicitMimeType, bool shouldPreferPlugInsForImages) { // This code is based on Apple's implementation from // WebCoreSupport/WebFrameBridge.mm. String mimeType = explicitMimeType; if (mimeType.isEmpty()) { // Try to guess the MIME type based off the extension. String filename = url.lastPathComponent(); int extensionPos = filename.reverseFind('.'); if (extensionPos >= 0) { String extension = filename.substring(extensionPos + 1); mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension); if (mimeType.isEmpty()) { // If there's no mimetype registered for the extension, check to see // if a plugin can handle the extension. mimeType = getPluginMimeTypeFromExtension(extension); } } if (mimeType.isEmpty()) return ObjectContentFrame; } // If Chrome is started with the --disable-plugins switch, pluginData is 0. PluginData* pluginData = m_webFrame->frame()->page()->pluginData(); bool plugInSupportsMIMEType = pluginData && pluginData->supportsMimeType(mimeType); if (MIMETypeRegistry::isSupportedImageMIMEType(mimeType)) return shouldPreferPlugInsForImages && plugInSupportsMIMEType ? ObjectContentNetscapePlugin : ObjectContentImage; if (plugInSupportsMIMEType) return ObjectContentNetscapePlugin; if (MIMETypeRegistry::isSupportedNonImageMIMEType(mimeType)) return ObjectContentFrame; return ObjectContentNone; } String FrameLoaderClientImpl::overrideMediaType() const { // FIXME return String(); } bool FrameLoaderClientImpl::actionSpecifiesNavigationPolicy( const NavigationAction& action, WebNavigationPolicy* policy) { const MouseEvent* event = 0; if (action.type() == NavigationTypeLinkClicked && action.event()->isMouseEvent()) event = static_cast(action.event()); else if (action.type() == NavigationTypeFormSubmitted && action.event() && action.event()->underlyingEvent() && action.event()->underlyingEvent()->isMouseEvent()) event = static_cast(action.event()->underlyingEvent()); if (!event) return false; return WebViewImpl::navigationPolicyFromMouseEvent( event->button(), event->ctrlKey(), event->shiftKey(), event->altKey(), event->metaKey(), policy); } PassOwnPtr FrameLoaderClientImpl::pluginLoadObserver() { WebDataSourceImpl* ds = WebDataSourceImpl::fromDocumentLoader( m_webFrame->frame()->loader()->activeDocumentLoader()); if (!ds) { // We can arrive here if a popstate event handler detaches this frame. // FIXME: Remove this code once http://webkit.org/b/36202 is fixed. ASSERT(!m_webFrame->frame()->page()); return 0; } return ds->releasePluginLoadObserver(); } PassRefPtr FrameLoaderClientImpl::createNetworkingContext() { return FrameNetworkingContextImpl::create(m_webFrame->frame()); } } // namespace WebKit