/* * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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 "FrameLoader.h" #if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size #include "Archive.h" #include "ArchiveFactory.h" #endif #include "CString.h" #include "Cache.h" #include "CachedPage.h" #include "Chrome.h" #include "DOMImplementation.h" #include "DOMWindow.h" #include "DocLoader.h" #include "Document.h" #include "DocumentLoader.h" #include "Editor.h" #include "EditorClient.h" #include "Element.h" #include "Event.h" #include "EventNames.h" #include "FloatRect.h" #include "FormState.h" #include "Frame.h" #include "FrameLoadRequest.h" #include "FrameLoaderClient.h" #include "FrameTree.h" #include "FrameView.h" #include "HTMLAnchorElement.h" #include "HTMLFormElement.h" #include "HTMLFrameElement.h" #include "HTMLNames.h" #include "HTMLObjectElement.h" #include "HTTPParsers.h" #include "HistoryItem.h" #include "IconDatabase.h" #include "IconLoader.h" #include "InspectorController.h" #include "Logging.h" #include "MIMETypeRegistry.h" #include "MainResourceLoader.h" #include "Page.h" #include "PageCache.h" #include "PageGroup.h" #include "PluginData.h" #include "PluginDocument.h" #include "ProgressTracker.h" #include "RenderPart.h" #include "RenderView.h" #include "RenderWidget.h" #include "ResourceHandle.h" #include "ResourceRequest.h" #include "ScriptController.h" #include "ScriptSourceCode.h" #include "ScriptValue.h" #include "SecurityOrigin.h" #include "SegmentedString.h" #include "Settings.h" #include "TextResourceDecoder.h" #include "WindowFeatures.h" #include "XMLHttpRequest.h" #include "XMLTokenizer.h" #include #include #if ENABLE(OFFLINE_WEB_APPLICATIONS) #include "ApplicationCache.h" #include "ApplicationCacheResource.h" #endif #if ENABLE(SVG) #include "SVGDocument.h" #include "SVGLocatable.h" #include "SVGNames.h" #include "SVGPreserveAspectRatio.h" #include "SVGSVGElement.h" #include "SVGViewElement.h" #include "SVGViewSpec.h" #endif #ifdef ANDROID_INSTRUMENT #include "TimeCounter.h" #include "RenderArena.h" #endif #if PLATFORM(ANDROID) #include "WebCoreFrameBridge.h" #endif namespace WebCore { #if ENABLE(SVG) using namespace SVGNames; #endif using namespace HTMLNames; #if USE(LOW_BANDWIDTH_DISPLAY) const unsigned int cMaxPendingSourceLengthInLowBandwidthDisplay = 128 * 1024; #endif typedef HashSet LocalSchemesMap; struct FormSubmission { FormSubmission(const char* action, const String& url, PassRefPtr formData, const String& target, const String& contentType, const String& boundary, PassRefPtr event, bool lockHistory, bool lockBackForwardList) : action(action) , url(url) , formData(formData) , target(target) , contentType(contentType) , boundary(boundary) , event(event) , lockHistory(lockHistory) , lockBackForwardList(lockBackForwardList) { } const char* action; String url; RefPtr formData; String target; String contentType; String boundary; RefPtr event; bool lockHistory; bool lockBackForwardList; }; struct ScheduledRedirection { enum Type { redirection, locationChange, historyNavigation, locationChangeDuringLoad }; Type type; double delay; String url; String referrer; int historySteps; bool lockHistory; bool lockBackForwardList; bool wasUserGesture; bool wasRefresh; ScheduledRedirection(double delay, const String& url, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh) : type(redirection) , delay(delay) , url(url) , historySteps(0) , lockHistory(lockHistory) , lockBackForwardList(lockBackForwardList) , wasUserGesture(wasUserGesture) , wasRefresh(refresh) { } ScheduledRedirection(Type locationChangeType, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh) : type(locationChangeType) , delay(0) , url(url) , referrer(referrer) , historySteps(0) , lockHistory(lockHistory) , lockBackForwardList(lockBackForwardList) , wasUserGesture(wasUserGesture) , wasRefresh(refresh) { } explicit ScheduledRedirection(int historyNavigationSteps) : type(historyNavigation) , delay(0) , historySteps(historyNavigationSteps) , lockHistory(false) , wasUserGesture(false) , wasRefresh(false) { } }; static double storedTimeOfLastCompletedLoad; static FrameLoader::LocalLoadPolicy localLoadPolicy = FrameLoader::AllowLocalLoadsForLocalOnly; bool isBackForwardLoadType(FrameLoadType type) { switch (type) { case FrameLoadTypeStandard: case FrameLoadTypeReload: case FrameLoadTypeReloadFromOrigin: case FrameLoadTypeSame: case FrameLoadTypeRedirectWithLockedBackForwardList: case FrameLoadTypeReplace: return false; case FrameLoadTypeBack: case FrameLoadTypeForward: case FrameLoadTypeIndexedBackForward: return true; } ASSERT_NOT_REACHED(); return false; } static int numRequests(Document* document) { if (!document) return 0; return document->docLoader()->requestCount(); } FrameLoader::FrameLoader(Frame* frame, FrameLoaderClient* client) : m_frame(frame) , m_client(client) , m_state(FrameStateCommittedPage) , m_loadType(FrameLoadTypeStandard) , m_policyLoadType(FrameLoadTypeStandard) , m_delegateIsHandlingProvisionalLoadError(false) , m_delegateIsDecidingNavigationPolicy(false) , m_delegateIsHandlingUnimplementablePolicy(false) , m_firstLayoutDone(false) , m_quickRedirectComing(false) , m_sentRedirectNotification(false) , m_inStopAllLoaders(false) , m_navigationDuringLoad(false) , m_isExecutingJavaScriptFormAction(false) , m_isRunningScript(false) , m_didCallImplicitClose(false) , m_wasUnloadEventEmitted(false) , m_isComplete(false) , m_isLoadingMainResource(false) , m_cancellingWithLoadInProgress(false) , m_needsClear(false) , m_receivedData(false) , m_encodingWasChosenByUser(false) , m_containsPlugIns(false) , m_redirectionTimer(this, &FrameLoader::redirectionTimerFired) , m_checkCompletedTimer(this, &FrameLoader::checkCompletedTimerFired) , m_checkLoadCompleteTimer(this, &FrameLoader::checkLoadCompleteTimerFired) , m_opener(0) , m_openedByDOM(false) , m_creatingInitialEmptyDocument(false) , m_isDisplayingInitialEmptyDocument(false) , m_committedFirstRealDocumentLoad(false) , m_didPerformFirstNavigation(false) #ifndef NDEBUG , m_didDispatchDidCommitLoad(false) #endif #if USE(LOW_BANDWIDTH_DISPLAY) , m_useLowBandwidthDisplay(true) , m_finishedParsingDuringLowBandwidthDisplay(false) , m_needToSwitchOutLowBandwidthDisplay(false) #endif #if ENABLE(WML) , m_forceReloadWmlDeck(false) #endif { } FrameLoader::~FrameLoader() { setOpener(0); HashSet::iterator end = m_openedFrames.end(); for (HashSet::iterator it = m_openedFrames.begin(); it != end; ++it) (*it)->loader()->m_opener = 0; m_client->frameLoaderDestroyed(); } void FrameLoader::init() { // this somewhat odd set of steps is needed to give the frame an initial empty document m_isDisplayingInitialEmptyDocument = false; m_creatingInitialEmptyDocument = true; setPolicyDocumentLoader(m_client->createDocumentLoader(ResourceRequest(KURL("")), SubstituteData()).get()); setProvisionalDocumentLoader(m_policyDocumentLoader.get()); setState(FrameStateProvisional); m_provisionalDocumentLoader->setResponse(ResourceResponse(KURL(), "text/html", 0, String(), String())); m_provisionalDocumentLoader->finishedLoading(); begin(KURL(), false); end(); m_frame->document()->cancelParsing(); m_creatingInitialEmptyDocument = false; m_didCallImplicitClose = true; } void FrameLoader::setDefersLoading(bool defers) { if (m_documentLoader) m_documentLoader->setDefersLoading(defers); if (m_provisionalDocumentLoader) m_provisionalDocumentLoader->setDefersLoading(defers); if (m_policyDocumentLoader) m_policyDocumentLoader->setDefersLoading(defers); } Frame* FrameLoader::createWindow(FrameLoader* frameLoaderForFrameLookup, const FrameLoadRequest& request, const WindowFeatures& features, bool& created) { ASSERT(!features.dialog || request.frameName().isEmpty()); if (!request.frameName().isEmpty() && request.frameName() != "_blank") { Frame* frame = frameLoaderForFrameLookup->frame()->tree()->find(request.frameName()); if (frame && shouldAllowNavigation(frame)) { if (!request.resourceRequest().url().isEmpty()) frame->loader()->loadFrameRequestWithFormAndValues(request, false, false, 0, 0, HashMap()); if (Page* page = frame->page()) page->chrome()->focus(); created = false; return frame; } } // FIXME: Setting the referrer should be the caller's responsibility. FrameLoadRequest requestWithReferrer = request; requestWithReferrer.resourceRequest().setHTTPReferrer(m_outgoingReferrer); addHTTPOriginIfNeeded(requestWithReferrer.resourceRequest(), outgoingOrigin()); Page* oldPage = m_frame->page(); if (!oldPage) return 0; Page* page = oldPage->chrome()->createWindow(m_frame, requestWithReferrer, features); if (!page) return 0; Frame* frame = page->mainFrame(); if (request.frameName() != "_blank") frame->tree()->setName(request.frameName()); page->chrome()->setToolbarsVisible(features.toolBarVisible || features.locationBarVisible); page->chrome()->setStatusbarVisible(features.statusBarVisible); page->chrome()->setScrollbarsVisible(features.scrollbarsVisible); page->chrome()->setMenubarVisible(features.menuBarVisible); page->chrome()->setResizable(features.resizable); // 'x' and 'y' specify the location of the window, while 'width' and 'height' // specify the size of the page. We can only resize the window, so // adjust for the difference between the window size and the page size. FloatRect windowRect = page->chrome()->windowRect(); FloatSize pageSize = page->chrome()->pageRect().size(); if (features.xSet) windowRect.setX(features.x); if (features.ySet) windowRect.setY(features.y); if (features.widthSet) windowRect.setWidth(features.width + (windowRect.width() - pageSize.width())); if (features.heightSet) windowRect.setHeight(features.height + (windowRect.height() - pageSize.height())); page->chrome()->setWindowRect(windowRect); page->chrome()->show(); created = true; return frame; } bool FrameLoader::canHandleRequest(const ResourceRequest& request) { return m_client->canHandleRequest(request); } void FrameLoader::changeLocation(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool userGesture, bool refresh) { changeLocation(completeURL(url), referrer, lockHistory, lockBackForwardList, userGesture, refresh); } void FrameLoader::changeLocation(const KURL& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool userGesture, bool refresh) { RefPtr protect(m_frame); ResourceRequest request(url, referrer, refresh ? ReloadIgnoringCacheData : UseProtocolCachePolicy); #ifdef ANDROID_USER_GESTURE request.setUserGesture(userGesture); #endif if (executeIfJavaScriptURL(request.url(), userGesture)) return; urlSelected(request, "_self", 0, lockHistory, lockBackForwardList, userGesture); } void FrameLoader::urlSelected(const FrameLoadRequest& request, Event* event, bool lockHistory, bool lockBackForwardList) { FrameLoadRequest copy = request; if (copy.resourceRequest().httpReferrer().isEmpty()) copy.resourceRequest().setHTTPReferrer(m_outgoingReferrer); addHTTPOriginIfNeeded(copy.resourceRequest(), outgoingOrigin()); loadFrameRequestWithFormAndValues(copy, lockHistory, lockBackForwardList, event, 0, HashMap()); } void FrameLoader::urlSelected(const ResourceRequest& request, const String& _target, Event* triggeringEvent, bool lockHistory, bool lockBackForwardList, bool userGesture) { if (executeIfJavaScriptURL(request.url(), userGesture, false)) return; String target = _target; if (target.isEmpty() && m_frame->document()) target = m_frame->document()->baseTarget(); FrameLoadRequest frameRequest(request, target); #ifdef ANDROID_USER_GESTURE frameRequest.setWasUserGesture(userGesture); #endif urlSelected(frameRequest, triggeringEvent, lockHistory, lockBackForwardList); } bool FrameLoader::requestFrame(HTMLFrameOwnerElement* ownerElement, const String& urlString, const AtomicString& frameName) { #if USE(LOW_BANDWIDTH_DISPLAY) // don't create sub-frame during low bandwidth display if (frame()->document()->inLowBandwidthDisplay()) { m_needToSwitchOutLowBandwidthDisplay = true; return false; } #endif // Support for KURL scriptURL; KURL url; if (protocolIs(urlString, "javascript")) { scriptURL = completeURL(urlString); // completeURL() encodes the URL. url = blankURL(); } else url = completeURL(urlString); Frame* frame = ownerElement->contentFrame(); if (frame) frame->loader()->scheduleLocationChange(url.string(), m_outgoingReferrer, true, true, userGestureHint()); else frame = loadSubframe(ownerElement, url, frameName, m_outgoingReferrer); if (!frame) return false; if (!scriptURL.isEmpty()) frame->loader()->executeIfJavaScriptURL(scriptURL); return true; } Frame* FrameLoader::loadSubframe(HTMLFrameOwnerElement* ownerElement, const KURL& url, const String& name, const String& referrer) { bool allowsScrolling = true; int marginWidth = -1; int marginHeight = -1; if (ownerElement->hasTagName(frameTag) || ownerElement->hasTagName(iframeTag)) { HTMLFrameElementBase* o = static_cast(ownerElement); allowsScrolling = o->scrollingMode() != ScrollbarAlwaysOff; marginWidth = o->getMarginWidth(); marginHeight = o->getMarginHeight(); } if (!canLoad(url, referrer)) { FrameLoader::reportLocalLoadFailed(m_frame, url.string()); return 0; } bool hideReferrer = shouldHideReferrer(url, referrer); RefPtr frame = m_client->createFrame(url, name, ownerElement, hideReferrer ? String() : referrer, allowsScrolling, marginWidth, marginHeight); if (!frame) { checkCallImplicitClose(); return 0; } frame->loader()->m_isComplete = false; RenderObject* renderer = ownerElement->renderer(); FrameView* view = frame->view(); if (renderer && renderer->isWidget() && view) static_cast(renderer)->setWidget(view); checkCallImplicitClose(); // In these cases, the synchronous load would have finished // before we could connect the signals, so make sure to send the // completed() signal for the child by hand // FIXME: In this case the Frame will have finished loading before // it's being added to the child list. It would be a good idea to // create the child first, then invoke the loader separately. if (url.isEmpty() || url == blankURL()) { frame->loader()->completed(); frame->loader()->checkCompleted(); } return frame.get(); } void FrameLoader::submitFormAgain() { if (m_isRunningScript) return; OwnPtr form(m_deferredFormSubmission.release()); if (!form) return; submitForm(form->action, form->url, form->formData, form->target, form->contentType, form->boundary, form->event.get(), form->lockHistory, form->lockBackForwardList); } void FrameLoader::submitForm(const char* action, const String& url, PassRefPtr formData, const String& target, const String& contentType, const String& boundary, Event* event, bool lockHistory, bool lockBackForwardList) { ASSERT(formData); if (!m_frame->page()) return; KURL u = completeURL(url.isNull() ? "" : url); // FIXME: Do we really need to special-case an empty URL? // Would it be better to just go on with the form submisson and let the I/O fail? if (u.isEmpty()) return; if (u.protocolIs("javascript")) { m_isExecutingJavaScriptFormAction = true; executeIfJavaScriptURL(u, false, false); m_isExecutingJavaScriptFormAction = false; return; } if (m_isRunningScript) { if (m_deferredFormSubmission) return; m_deferredFormSubmission.set(new FormSubmission(action, url, formData, target, contentType, boundary, event, lockHistory, lockBackForwardList)); return; } formData->generateFiles(m_frame->page()->chrome()->client()); FrameLoadRequest frameRequest; #ifdef ANDROID_USER_GESTURE frameRequest.setWasUserGesture(userGestureHint()); #endif if (!m_outgoingReferrer.isEmpty()) frameRequest.resourceRequest().setHTTPReferrer(m_outgoingReferrer); frameRequest.setFrameName(target.isEmpty() ? m_frame->document()->baseTarget() : target); // Handle mailto: forms bool isMailtoForm = equalIgnoringCase(u.protocol(), "mailto"); if (isMailtoForm && strcmp(action, "GET") != 0) { // Append body= for POST mailto, replace the whole query string for GET one. String body = formData->flattenToString(); String query = u.query(); if (!query.isEmpty()) query.append('&'); u.setQuery(query + body); } if (strcmp(action, "GET") == 0) { u.setQuery(formData->flattenToString()); } else { if (!isMailtoForm) frameRequest.resourceRequest().setHTTPBody(formData.get()); frameRequest.resourceRequest().setHTTPMethod("POST"); // construct some user headers if necessary if (contentType.isNull() || contentType == "application/x-www-form-urlencoded") frameRequest.resourceRequest().setHTTPContentType(contentType); else // contentType must be "multipart/form-data" frameRequest.resourceRequest().setHTTPContentType(contentType + "; boundary=" + boundary); } frameRequest.resourceRequest().setURL(u); addHTTPOriginIfNeeded(frameRequest.resourceRequest(), outgoingOrigin()); submitForm(frameRequest, event, lockHistory, lockBackForwardList); } void FrameLoader::stopLoading(bool sendUnload) { if (m_frame->document() && m_frame->document()->tokenizer()) m_frame->document()->tokenizer()->stopParsing(); if (sendUnload) { if (m_frame->document()) { if (m_didCallImplicitClose && !m_wasUnloadEventEmitted) { Node* currentFocusedNode = m_frame->document()->focusedNode(); if (currentFocusedNode) currentFocusedNode->aboutToUnload(); m_frame->document()->dispatchWindowEvent(eventNames().unloadEvent, false, false); if (m_frame->document()) m_frame->document()->updateRendering(); m_wasUnloadEventEmitted = true; if (m_frame->eventHandler()->pendingFrameUnloadEventCount()) m_frame->eventHandler()->clearPendingFrameUnloadEventCount(); if (m_frame->eventHandler()->pendingFrameBeforeUnloadEventCount()) m_frame->eventHandler()->clearPendingFrameBeforeUnloadEventCount(); } } if (m_frame->document() && !m_frame->document()->inPageCache()) m_frame->document()->removeAllEventListenersFromAllNodes(); } m_isComplete = true; // to avoid calling completed() in finishedParsing() (David) m_isLoadingMainResource = false; m_didCallImplicitClose = true; // don't want that one either if (m_frame->document() && m_frame->document()->parsing()) { finishedParsing(); m_frame->document()->setParsing(false); } m_workingURL = KURL(); if (Document* doc = m_frame->document()) { if (DocLoader* docLoader = doc->docLoader()) cache()->loader()->cancelRequests(docLoader); #if ENABLE(DATABASE) doc->stopDatabases(); #endif } // tell all subframes to stop as well for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) child->loader()->stopLoading(sendUnload); cancelRedirection(); #if USE(LOW_BANDWIDTH_DISPLAY) if (m_frame->document() && m_frame->document()->inLowBandwidthDisplay()) { // Since loading is forced to stop, reset the state without really switching. m_needToSwitchOutLowBandwidthDisplay = false; switchOutLowBandwidthDisplayIfReady(); } #endif } void FrameLoader::stop() { // http://bugs.webkit.org/show_bug.cgi?id=10854 // The frame's last ref may be removed and it will be deleted by checkCompleted(). RefPtr protector(m_frame); if (m_frame->document()) { if (m_frame->document()->tokenizer()) m_frame->document()->tokenizer()->stopParsing(); m_frame->document()->finishParsing(); } else // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to // become true. An example is when a subframe is a pure text doc, and that subframe is the // last one to complete. checkCompleted(); if (m_iconLoader) m_iconLoader->stopLoading(); } bool FrameLoader::closeURL() { saveDocumentState(); stopLoading(true); m_frame->editor()->clearUndoRedoOperations(); return true; } void FrameLoader::cancelRedirection(bool cancelWithLoadInProgress) { m_cancellingWithLoadInProgress = cancelWithLoadInProgress; stopRedirectionTimer(); m_scheduledRedirection.clear(); } KURL FrameLoader::iconURL() { // If this isn't a top level frame, return nothing if (m_frame->tree() && m_frame->tree()->parent()) return KURL(); // If we have an iconURL from a Link element, return that if (m_frame->document() && !m_frame->document()->iconURL().isEmpty()) return KURL(m_frame->document()->iconURL()); // Don't return a favicon iconURL unless we're http or https if (!m_URL.protocolIs("http") && !m_URL.protocolIs("https")) return KURL(); KURL url; url.setProtocol(m_URL.protocol()); url.setHost(m_URL.host()); if (int port = m_URL.port()) url.setPort(port); url.setPath("/favicon.ico"); return url; } bool FrameLoader::didOpenURL(const KURL& url) { if (m_scheduledRedirection && m_scheduledRedirection->type == ScheduledRedirection::locationChangeDuringLoad) // A redirect was scheduled before the document was created. // This can happen when one frame changes another frame's location. return false; cancelRedirection(); m_frame->editor()->clearLastEditCommand(); closeURL(); m_isComplete = false; m_isLoadingMainResource = true; m_didCallImplicitClose = false; m_frame->setJSStatusBarText(String()); m_frame->setJSDefaultStatusBarText(String()); m_URL = url; if ((m_URL.protocolIs("http") || m_URL.protocolIs("https")) && !m_URL.host().isEmpty() && m_URL.path().isEmpty()) m_URL.setPath("/"); m_workingURL = m_URL; started(); return true; } void FrameLoader::didExplicitOpen() { m_isComplete = false; m_didCallImplicitClose = false; // Calling document.open counts as committing the first real document load. m_committedFirstRealDocumentLoad = true; // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing away results // from a subsequent window.document.open / window.document.write call. // Cancelling redirection here works for all cases because document.open // implicitly precedes document.write. cancelRedirection(); if (m_frame->document()->url() != blankURL()) m_URL = m_frame->document()->url(); } bool FrameLoader::executeIfJavaScriptURL(const KURL& url, bool userGesture, bool replaceDocument) { if (!url.protocolIs("javascript")) return false; String script = decodeURLEscapeSequences(url.string().substring(strlen("javascript:"))); ScriptValue result = executeScript(script, userGesture); String scriptResult; if (!result.getString(scriptResult)) return true; SecurityOrigin* currentSecurityOrigin = 0; if (m_frame->document()) currentSecurityOrigin = m_frame->document()->securityOrigin(); // FIXME: We should always replace the document, but doing so // synchronously can cause crashes: // http://bugs.webkit.org/show_bug.cgi?id=16782 if (replaceDocument) { begin(m_URL, true, currentSecurityOrigin); write(scriptResult); end(); } return true; } ScriptValue FrameLoader::executeScript(const String& script, bool forceUserGesture) { return executeScript(ScriptSourceCode(script, forceUserGesture ? KURL() : m_URL)); } ScriptValue FrameLoader::executeScript(const ScriptSourceCode& sourceCode) { if (!m_frame->script()->isEnabled() || m_frame->script()->isPaused()) return ScriptValue(); bool wasRunningScript = m_isRunningScript; m_isRunningScript = true; ScriptValue result = m_frame->script()->evaluate(sourceCode); if (!wasRunningScript) { m_isRunningScript = false; submitFormAgain(); Document::updateDocumentsRendering(); } return result; } void FrameLoader::cancelAndClear() { cancelRedirection(); if (!m_isComplete) closeURL(); clear(false); m_frame->script()->updatePlatformScriptObjects(); } void FrameLoader::clear(bool clearWindowProperties, bool clearScriptObjects) { m_frame->editor()->clear(); if (!m_needsClear) return; m_needsClear = false; if (m_frame->document() && !m_frame->document()->inPageCache()) { m_frame->document()->cancelParsing(); m_frame->document()->stopActiveDOMObjects(); if (m_frame->document()->attached()) { m_frame->document()->willRemove(); m_frame->document()->detach(); m_frame->document()->removeFocusedNodeOfSubtree(m_frame->document()); } } // Do this after detaching the document so that the unload event works. if (clearWindowProperties) { m_frame->clearDOMWindow(); m_frame->script()->clearWindowShell(); } m_frame->selection()->clear(); m_frame->eventHandler()->clear(); if (m_frame->view()) m_frame->view()->clear(); m_frame->setSelectionGranularity(CharacterGranularity); // Do not drop the document before the ScriptController and view are cleared // as some destructors might still try to access the document. m_frame->setDocument(0); m_decoder = 0; m_containsPlugIns = false; if (clearScriptObjects) m_frame->script()->clearScriptObjects(); m_redirectionTimer.stop(); m_scheduledRedirection.clear(); m_checkCompletedTimer.stop(); m_checkLoadCompleteTimer.stop(); m_receivedData = false; m_isDisplayingInitialEmptyDocument = false; if (!m_encodingWasChosenByUser) m_encoding = String(); } void FrameLoader::receivedFirstData() { begin(m_workingURL, false); dispatchDidCommitLoad(); dispatchWindowObjectAvailable(); String ptitle = m_documentLoader->title(); // If we have a title let the WebView know about it. if (!ptitle.isNull()) m_client->dispatchDidReceiveTitle(ptitle); m_workingURL = KURL(); double delay; String url; if (!m_documentLoader) return; if (!parseHTTPRefresh(m_documentLoader->response().httpHeaderField("Refresh"), false, delay, url)) return; if (url.isEmpty()) url = m_URL.string(); else url = m_frame->document()->completeURL(url).string(); scheduleHTTPRedirection(delay, url); } const String& FrameLoader::responseMIMEType() const { return m_responseMIMEType; } void FrameLoader::setResponseMIMEType(const String& type) { m_responseMIMEType = type; } void FrameLoader::begin() { begin(KURL()); } void FrameLoader::begin(const KURL& url, bool dispatch, SecurityOrigin* origin) { // We need to take a reference to the security origin because |clear| // might destroy the document that owns it. RefPtr forcedSecurityOrigin = origin; bool resetScripting = !(m_isDisplayingInitialEmptyDocument && m_frame->document() && m_frame->document()->securityOrigin()->isSecureTransitionTo(url)); clear(resetScripting, resetScripting); if (resetScripting) m_frame->script()->updatePlatformScriptObjects(); if (dispatch) dispatchWindowObjectAvailable(); m_needsClear = true; m_isComplete = false; m_didCallImplicitClose = false; m_isLoadingMainResource = true; m_isDisplayingInitialEmptyDocument = m_creatingInitialEmptyDocument; KURL ref(url); ref.setUser(String()); ref.setPass(String()); ref.setRef(String()); m_outgoingReferrer = ref.string(); m_URL = url; RefPtr document; if (!m_isDisplayingInitialEmptyDocument && m_client->shouldUsePluginDocument(m_responseMIMEType)) document = PluginDocument::create(m_frame); else document = DOMImplementation::createDocument(m_responseMIMEType, m_frame, m_frame->inViewSourceMode()); m_frame->setDocument(document); document->setURL(m_URL); if (m_decoder) document->setDecoder(m_decoder.get()); if (forcedSecurityOrigin) document->setSecurityOrigin(forcedSecurityOrigin.get()); m_frame->domWindow()->setURL(document->url()); m_frame->domWindow()->setSecurityOrigin(document->securityOrigin()); updatePolicyBaseURL(); Settings* settings = document->settings(); document->docLoader()->setAutoLoadImages(settings && settings->loadsImagesAutomatically()); #ifdef ANDROID_BLOCK_NETWORK_IMAGE document->docLoader()->setBlockNetworkImage(settings && settings->blockNetworkImage()); #endif if (m_documentLoader) { String dnsPrefetchControl = m_documentLoader->response().httpHeaderField("X-DNS-Prefetch-Control"); if (!dnsPrefetchControl.isEmpty()) document->parseDNSPrefetchControlHeader(dnsPrefetchControl); } #if FRAME_LOADS_USER_STYLESHEET KURL userStyleSheet = settings ? settings->userStyleSheetLocation() : KURL(); if (!userStyleSheet.isEmpty()) m_frame->setUserStyleSheetLocation(userStyleSheet); #endif restoreDocumentState(); document->implicitOpen(); if (m_frame->view()) m_frame->view()->setContentsSize(IntSize()); #if USE(LOW_BANDWIDTH_DISPLAY) // Low bandwidth display is a first pass display without external resources // used to give an instant visual feedback. We currently only enable it for // HTML documents in the top frame. if (document->isHTMLDocument() && !m_frame->tree()->parent() && m_useLowBandwidthDisplay) { m_pendingSourceInLowBandwidthDisplay = String(); m_finishedParsingDuringLowBandwidthDisplay = false; m_needToSwitchOutLowBandwidthDisplay = false; document->setLowBandwidthDisplay(true); } #endif } void FrameLoader::write(const char* str, int len, bool flush) { if (len == 0 && !flush) return; if (len == -1) len = strlen(str); Tokenizer* tokenizer = m_frame->document()->tokenizer(); if (tokenizer && tokenizer->wantsRawData()) { if (len > 0) tokenizer->writeRawData(str, len); return; } if (!m_decoder) { Settings* settings = m_frame->settings(); m_decoder = TextResourceDecoder::create(m_responseMIMEType, settings ? settings->defaultTextEncodingName() : String()); if (m_encoding.isEmpty()) { Frame* parentFrame = m_frame->tree()->parent(); if (parentFrame && parentFrame->document()->securityOrigin()->canAccess(m_frame->document()->securityOrigin())) m_decoder->setEncoding(parentFrame->document()->inputEncoding(), TextResourceDecoder::DefaultEncoding); } else { m_decoder->setEncoding(m_encoding, m_encodingWasChosenByUser ? TextResourceDecoder::UserChosenEncoding : TextResourceDecoder::EncodingFromHTTPHeader); } m_frame->document()->setDecoder(m_decoder.get()); } String decoded = m_decoder->decode(str, len); if (flush) decoded += m_decoder->flush(); if (decoded.isEmpty()) return; #if USE(LOW_BANDWIDTH_DISPLAY) if (m_frame->document()->inLowBandwidthDisplay()) m_pendingSourceInLowBandwidthDisplay.append(decoded); #endif if (!m_receivedData) { m_receivedData = true; if (m_decoder->encoding().usesVisualOrdering()) m_frame->document()->setVisuallyOrdered(); m_frame->document()->recalcStyle(Node::Force); } if (tokenizer) { ASSERT(!tokenizer->wantsRawData()); tokenizer->write(decoded, true); } } void FrameLoader::write(const String& str) { if (str.isNull()) return; if (!m_receivedData) { m_receivedData = true; m_frame->document()->setParseMode(Document::Strict); } if (Tokenizer* tokenizer = m_frame->document()->tokenizer()) tokenizer->write(str, true); } void FrameLoader::end() { m_isLoadingMainResource = false; endIfNotLoadingMainResource(); } void FrameLoader::endIfNotLoadingMainResource() { if (m_isLoadingMainResource || !m_frame->page()) return; // http://bugs.webkit.org/show_bug.cgi?id=10854 // The frame's last ref may be removed and it can be deleted by checkCompleted(), // so we'll add a protective refcount RefPtr protector(m_frame); // make sure nothing's left in there if (m_frame->document()) { write(0, 0, true); m_frame->document()->finishParsing(); #if USE(LOW_BANDWIDTH_DISPLAY) if (m_frame->document()->inLowBandwidthDisplay()) { m_finishedParsingDuringLowBandwidthDisplay = true; switchOutLowBandwidthDisplayIfReady(); } #endif } else // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to // become true. An example is when a subframe is a pure text doc, and that subframe is the // last one to complete. checkCompleted(); } void FrameLoader::iconLoadDecisionAvailable() { if (!m_mayLoadIconLater) return; LOG(IconDatabase, "FrameLoader %p was told a load decision is available for its icon", this); startIconLoader(); m_mayLoadIconLater = false; } void FrameLoader::startIconLoader() { // FIXME: We kick off the icon loader when the frame is done receiving its main resource. // But we should instead do it when we're done parsing the head element. if (!isLoadingMainFrame()) return; if (!iconDatabase() || !iconDatabase()->isEnabled()) return; KURL url(iconURL()); String urlString(url.string()); if (urlString.isEmpty()) return; // If we're not reloading and the icon database doesn't say to load now then bail before we actually start the load if (loadType() != FrameLoadTypeReload && loadType() != FrameLoadTypeReloadFromOrigin) { IconLoadDecision decision = iconDatabase()->loadDecisionForIconURL(urlString, m_documentLoader.get()); if (decision == IconLoadNo) { LOG(IconDatabase, "FrameLoader::startIconLoader() - Told not to load this icon, committing iconURL %s to database for pageURL mapping", urlString.ascii().data()); commitIconURLToIconDatabase(url); // We were told not to load this icon - that means this icon is already known by the database // If the icon data hasn't been read in from disk yet, kick off the read of the icon from the database to make sure someone // has done it. This is after registering for the notification so the WebView can call the appropriate delegate method. // Otherwise if the icon data *is* available, notify the delegate if (!iconDatabase()->iconDataKnownForIconURL(urlString)) { LOG(IconDatabase, "Told not to load icon %s but icon data is not yet available - registering for notification and requesting load from disk", urlString.ascii().data()); m_client->registerForIconNotification(); iconDatabase()->iconForPageURL(m_URL.string(), IntSize(0, 0)); iconDatabase()->iconForPageURL(originalRequestURL().string(), IntSize(0, 0)); } else m_client->dispatchDidReceiveIcon(); return; } if (decision == IconLoadUnknown) { // In this case, we may end up loading the icon later, but we still want to commit the icon url mapping to the database // just in case we don't end up loading later - if we commit the mapping a second time after the load, that's no big deal // We also tell the client to register for the notification that the icon is received now so it isn't missed in case the // icon is later read in from disk LOG(IconDatabase, "FrameLoader %p might load icon %s later", this, urlString.ascii().data()); m_mayLoadIconLater = true; m_client->registerForIconNotification(); commitIconURLToIconDatabase(url); return; } } // This is either a reload or the icon database said "yes, load the icon", so kick off the load! if (!m_iconLoader) m_iconLoader.set(IconLoader::create(m_frame).release()); m_iconLoader->startLoading(); } void FrameLoader::setLocalLoadPolicy(LocalLoadPolicy policy) { localLoadPolicy = policy; } bool FrameLoader::restrictAccessToLocal() { return localLoadPolicy != FrameLoader::AllowLocalLoadsForAll; } bool FrameLoader::allowSubstituteDataAccessToLocal() { return localLoadPolicy != FrameLoader::AllowLocalLoadsForLocalOnly; } static LocalSchemesMap& localSchemes() { DEFINE_STATIC_LOCAL(LocalSchemesMap, localSchemes, ()); if (localSchemes.isEmpty()) { localSchemes.add("file"); #if PLATFORM(MAC) localSchemes.add("applewebdata"); #endif #if PLATFORM(QT) localSchemes.add("qrc"); #endif } return localSchemes; } void FrameLoader::commitIconURLToIconDatabase(const KURL& icon) { ASSERT(iconDatabase()); LOG(IconDatabase, "Committing iconURL %s to database for pageURLs %s and %s", icon.string().ascii().data(), m_URL.string().ascii().data(), originalRequestURL().string().ascii().data()); iconDatabase()->setIconURLForPageURL(icon.string(), m_URL.string()); iconDatabase()->setIconURLForPageURL(icon.string(), originalRequestURL().string()); } void FrameLoader::restoreDocumentState() { Document* doc = m_frame->document(); if (!doc) return; HistoryItem* itemToRestore = 0; switch (loadType()) { case FrameLoadTypeReload: case FrameLoadTypeReloadFromOrigin: case FrameLoadTypeSame: case FrameLoadTypeReplace: break; case FrameLoadTypeBack: case FrameLoadTypeForward: case FrameLoadTypeIndexedBackForward: case FrameLoadTypeRedirectWithLockedBackForwardList: case FrameLoadTypeStandard: itemToRestore = m_currentHistoryItem.get(); } if (!itemToRestore) return; LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->name().string().utf8().data(), itemToRestore); doc->setStateForNewFormElements(itemToRestore->documentState()); } void FrameLoader::gotoAnchor() { // If our URL has no ref, then we have no place we need to jump to. // OTOH If CSS target was set previously, we want to set it to 0, recalc // and possibly repaint because :target pseudo class may have been // set (see bug 11321). if (!m_URL.hasRef() && !(m_frame->document() && m_frame->document()->getCSSTarget())) return; String ref = m_URL.ref(); if (gotoAnchor(ref)) return; // Try again after decoding the ref, based on the document's encoding. if (m_decoder) gotoAnchor(decodeURLEscapeSequences(ref, m_decoder->encoding())); } void FrameLoader::finishedParsing() { if (m_creatingInitialEmptyDocument) return; // This can be called from the Frame's destructor, in which case we shouldn't protect ourselves // because doing so will cause us to re-enter the destructor when protector goes out of scope. // Null-checking the FrameView indicates whether or not we're in the destructor. RefPtr protector = m_frame->view() ? m_frame : 0; m_client->dispatchDidFinishDocumentLoad(); checkCompleted(); if (!m_frame->view()) return; // We are being destroyed by something checkCompleted called. // Check if the scrollbars are really needed for the content. // If not, remove them, relayout, and repaint. m_frame->view()->restoreScrollbar(); gotoAnchor(); } void FrameLoader::loadDone() { if (m_frame->document()) checkCompleted(); } void FrameLoader::checkCompleted() { // Any frame that hasn't completed yet? for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) if (!child->loader()->m_isComplete) return; // Have we completed before? if (m_isComplete) return; // Are we still parsing? if (m_frame->document() && m_frame->document()->parsing()) return; // Still waiting for images/scripts? if (m_frame->document()) if (numRequests(m_frame->document())) return; #if USE(LOW_BANDWIDTH_DISPLAY) // as switch will be called, don't complete yet if (m_frame->document() && m_frame->document()->inLowBandwidthDisplay() && m_needToSwitchOutLowBandwidthDisplay) return; #endif // OK, completed. m_isComplete = true; RefPtr protect(m_frame); checkCallImplicitClose(); // if we didn't do it before // Do not start a redirection timer for subframes here. // That is deferred until the parent is completed. if (m_scheduledRedirection && !m_frame->tree()->parent()) startRedirectionTimer(); completed(); if (m_frame->page()) checkLoadComplete(); } void FrameLoader::checkCompletedTimerFired(Timer*) { checkCompleted(); } void FrameLoader::scheduleCheckCompleted() { if (!m_checkCompletedTimer.isActive()) m_checkCompletedTimer.startOneShot(0); } void FrameLoader::checkLoadCompleteTimerFired(Timer*) { if (!m_frame->page()) return; checkLoadComplete(); } void FrameLoader::scheduleCheckLoadComplete() { if (!m_checkLoadCompleteTimer.isActive()) m_checkLoadCompleteTimer.startOneShot(0); } void FrameLoader::checkCallImplicitClose() { if (m_didCallImplicitClose || !m_frame->document() || m_frame->document()->parsing()) return; for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) if (!child->loader()->m_isComplete) // still got a frame running -> too early return; m_didCallImplicitClose = true; m_wasUnloadEventEmitted = false; if (m_frame->document()) m_frame->document()->implicitClose(); } KURL FrameLoader::baseURL() const { ASSERT(m_frame->document()); return m_frame->document()->baseURL(); } String FrameLoader::baseTarget() const { ASSERT(m_frame->document()); return m_frame->document()->baseTarget(); } KURL FrameLoader::completeURL(const String& url) { ASSERT(m_frame->document()); return m_frame->document()->completeURL(url); } void FrameLoader::scheduleHTTPRedirection(double delay, const String& url) { if (delay < 0 || delay > INT_MAX / 1000) return; if (!m_frame->page()) return; // We want a new history item if the refresh timeout is > 1 second. if (!m_scheduledRedirection || delay <= m_scheduledRedirection->delay) #ifdef ANDROID_USER_GESTURE { bool wasUserGesture = false; DocumentLoader* docLoader = activeDocumentLoader(); if (docLoader) wasUserGesture = docLoader->request().userGesture(); scheduleRedirection(new ScheduledRedirection(delay, url, true, delay <= 1, wasUserGesture, false)); } #else scheduleRedirection(new ScheduledRedirection(delay, url, true, delay <= 1, false, false)); #endif } void FrameLoader::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture) { if (!m_frame->page()) return; // If the URL we're going to navigate to is the same as the current one, except for the // fragment part, we don't need to schedule the location change. KURL parsedURL(url); if (parsedURL.hasRef() && equalIgnoringRef(m_URL, parsedURL)) { changeLocation(url, referrer, lockHistory, lockBackForwardList, wasUserGesture); return; } // Handle a location change of a page with no document as a special case. // This may happen when a frame changes the location of another frame. bool duringLoad = !m_committedFirstRealDocumentLoad; // If a redirect was scheduled during a load, then stop the current load. // Otherwise when the current load transitions from a provisional to a // committed state, pending redirects may be cancelled. if (duringLoad) { if (m_provisionalDocumentLoader) m_provisionalDocumentLoader->stopLoading(); stopLoading(true); } ScheduledRedirection::Type type = duringLoad ? ScheduledRedirection::locationChangeDuringLoad : ScheduledRedirection::locationChange; scheduleRedirection(new ScheduledRedirection(type, url, referrer, lockHistory, lockBackForwardList, wasUserGesture, false)); } void FrameLoader::scheduleRefresh(bool wasUserGesture) { if (!m_frame->page()) return; // Handle a location change of a page with no document as a special case. // This may happen when a frame requests a refresh of another frame. bool duringLoad = !m_frame->document(); // If a refresh was scheduled during a load, then stop the current load. // Otherwise when the current load transitions from a provisional to a // committed state, pending redirects may be cancelled. if (duringLoad) stopLoading(true); ScheduledRedirection::Type type = duringLoad ? ScheduledRedirection::locationChangeDuringLoad : ScheduledRedirection::locationChange; scheduleRedirection(new ScheduledRedirection(type, m_URL.string(), m_outgoingReferrer, true, true, wasUserGesture, true)); } bool FrameLoader::isLocationChange(const ScheduledRedirection& redirection) { switch (redirection.type) { case ScheduledRedirection::redirection: return false; case ScheduledRedirection::historyNavigation: case ScheduledRedirection::locationChange: case ScheduledRedirection::locationChangeDuringLoad: return true; } ASSERT_NOT_REACHED(); return false; } void FrameLoader::scheduleHistoryNavigation(int steps) { if (!m_frame->page()) return; // navigation will always be allowed in the 0 steps case, which is OK because that's supposed to force a reload. if (!canGoBackOrForward(steps)) { cancelRedirection(); return; } // If the steps to navigate is not zero (which needs to force a reload), and if we think the navigation is going to be a fragment load // (when the URL we're going to navigate to is the same as the current one, except for the fragment part - but not exactly the same because that's a reload), // then we don't need to schedule the navigation. if (steps != 0) { KURL destination = historyURL(steps); // FIXME: This doesn't seem like a reliable way to tell whether or not the load will be a fragment load. if (equalIgnoringRef(m_URL, destination) && m_URL != destination) { goBackOrForward(steps); return; } } scheduleRedirection(new ScheduledRedirection(steps)); } void FrameLoader::goBackOrForward(int distance) { if (distance == 0) return; Page* page = m_frame->page(); if (!page) return; BackForwardList* list = page->backForwardList(); if (!list) return; HistoryItem* item = list->itemAtIndex(distance); if (!item) { if (distance > 0) { int forwardListCount = list->forwardListCount(); if (forwardListCount > 0) item = list->itemAtIndex(forwardListCount); } else { int backListCount = list->backListCount(); if (backListCount > 0) item = list->itemAtIndex(-backListCount); } } ASSERT(item); // we should not reach this line with an empty back/forward list if (item) page->goToItem(item, FrameLoadTypeIndexedBackForward); } void FrameLoader::redirectionTimerFired(Timer*) { ASSERT(m_frame->page()); OwnPtr redirection(m_scheduledRedirection.release()); switch (redirection->type) { case ScheduledRedirection::redirection: case ScheduledRedirection::locationChange: case ScheduledRedirection::locationChangeDuringLoad: changeLocation(redirection->url, redirection->referrer, redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, redirection->wasRefresh); return; case ScheduledRedirection::historyNavigation: if (redirection->historySteps == 0) { // Special case for go(0) from a frame -> reload only the frame urlSelected(m_URL, "", 0, redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture); return; } // go(i!=0) from a frame navigates into the history of the frame only, // in both IE and NS (but not in Mozilla). We can't easily do that. goBackOrForward(redirection->historySteps); return; } ASSERT_NOT_REACHED(); } /* In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree. The item that was the target of the user's navigation is designated as the "targetItem". When this method is called with doClip=YES we're able to create the whole tree except for the target's children, which will be loaded in the future. That part of the tree will be filled out as the child loads are committed. */ void FrameLoader::loadURLIntoChildFrame(const KURL& url, const String& referer, Frame* childFrame) { ASSERT(childFrame); HistoryItem* parentItem = currentHistoryItem(); FrameLoadType loadType = this->loadType(); FrameLoadType childLoadType = FrameLoadTypeRedirectWithLockedBackForwardList; KURL workingURL = url; // If we're moving in the backforward list, we might want to replace the content // of this child frame with whatever was there at that point. if (parentItem && parentItem->children().size() && isBackForwardLoadType(loadType)) { HistoryItem* childItem = parentItem->childItemWithName(childFrame->tree()->name()); if (childItem) { // Use the original URL to ensure we get all the side-effects, such as // onLoad handlers, of any redirects that happened. An example of where // this is needed is Radar 3213556. workingURL = KURL(childItem->originalURLString()); childLoadType = loadType; childFrame->loader()->setProvisionalHistoryItem(childItem); } } #if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size RefPtr subframeArchive = activeDocumentLoader()->popArchiveForSubframe(childFrame->tree()->name()); if (subframeArchive) childFrame->loader()->loadArchive(subframeArchive.release()); else #endif #ifdef ANDROID_USER_GESTURE childFrame->loader()->loadURL(workingURL, referer, String(), false, childLoadType, 0, 0, false); #else childFrame->loader()->loadURL(workingURL, referer, String(), false, childLoadType, 0, 0); #endif } #if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size void FrameLoader::loadArchive(PassRefPtr prpArchive) { RefPtr archive = prpArchive; ArchiveResource* mainResource = archive->mainResource(); ASSERT(mainResource); if (!mainResource) return; SubstituteData substituteData(mainResource->data(), mainResource->mimeType(), mainResource->textEncoding(), KURL()); ResourceRequest request(mainResource->url()); #if PLATFORM(MAC) request.applyWebArchiveHackForMail(); #endif RefPtr documentLoader = m_client->createDocumentLoader(request, substituteData); documentLoader->addAllArchiveResources(archive.get()); load(documentLoader.get()); } #endif String FrameLoader::encoding() const { if (m_encodingWasChosenByUser && !m_encoding.isEmpty()) return m_encoding; if (m_decoder && m_decoder->encoding().isValid()) return m_decoder->encoding().name(); Settings* settings = m_frame->settings(); return settings ? settings->defaultTextEncodingName() : String(); } bool FrameLoader::gotoAnchor(const String& name) { ASSERT(m_frame->document()); if (!m_frame->document()->haveStylesheetsLoaded()) { m_frame->document()->setGotoAnchorNeededAfterStylesheetsLoad(true); return false; } m_frame->document()->setGotoAnchorNeededAfterStylesheetsLoad(false); Element* anchorNode = m_frame->document()->findAnchor(name); #if ENABLE(SVG) if (m_frame->document()->isSVGDocument()) { if (name.startsWith("xpointer(")) { // We need to parse the xpointer reference here } else if (name.startsWith("svgView(")) { RefPtr svg = static_cast(m_frame->document())->rootElement(); if (!svg->currentView()->parseViewSpec(name)) return false; svg->setUseCurrentView(true); } else { if (anchorNode && anchorNode->hasTagName(SVGNames::viewTag)) { RefPtr viewElement = anchorNode->hasTagName(SVGNames::viewTag) ? static_cast(anchorNode) : 0; if (viewElement.get()) { RefPtr svg = static_cast(SVGLocatable::nearestViewportElement(viewElement.get())); svg->inheritViewAttributes(viewElement.get()); } } } // FIXME: need to decide which to focus on, and zoom to that one // FIXME: need to actually "highlight" the viewTarget(s) } #endif m_frame->document()->setCSSTarget(anchorNode); // Setting to null will clear the current target. // Implement the rule that "" and "top" both mean top of page as in other browsers. if (!anchorNode && !(name.isEmpty() || equalIgnoringCase(name, "top"))) return false; // We need to update the layout before scrolling, otherwise we could // really mess things up if an anchor scroll comes at a bad moment. if (m_frame->document()) { m_frame->document()->updateRendering(); // Only do a layout if changes have occurred that make it necessary. if (m_frame->view() && m_frame->contentRenderer() && m_frame->contentRenderer()->needsLayout()) m_frame->view()->layout(); } // Scroll nested layers and frames to reveal the anchor. // Align to the top and to the closest side (this matches other browsers). RenderObject* renderer; IntRect rect; if (!anchorNode) renderer = m_frame->document()->renderer(); // top of document else { renderer = anchorNode->renderer(); rect = anchorNode->getRect(); } #ifdef ANDROID_SCROLL_ON_GOTO_ANCHOR android::WebFrame::getWebFrame(m_frame)->setUserInitiatedClick(true); #endif if (renderer) renderer->enclosingLayer()->scrollRectToVisible(rect, true, RenderLayer::gAlignToEdgeIfNeeded, RenderLayer::gAlignTopAlways); #ifdef ANDROID_SCROLL_ON_GOTO_ANCHOR android::WebFrame::getWebFrame(m_frame)->setUserInitiatedClick(false); #endif return true; } bool FrameLoader::requestObject(RenderPart* renderer, const String& url, const AtomicString& frameName, const String& mimeType, const Vector& paramNames, const Vector& paramValues) { if (url.isEmpty() && mimeType.isEmpty()) return false; #if USE(LOW_BANDWIDTH_DISPLAY) // don't care object during low bandwidth display if (frame()->document()->inLowBandwidthDisplay()) { m_needToSwitchOutLowBandwidthDisplay = true; return false; } #endif KURL completedURL; if (!url.isEmpty()) completedURL = completeURL(url); bool useFallback; if (shouldUsePlugin(completedURL, mimeType, renderer->hasFallbackContent(), useFallback)) { Settings* settings = m_frame->settings(); if (!settings || !settings->arePluginsEnabled() || (!settings->isJavaEnabled() && MIMETypeRegistry::isJavaAppletMIMEType(mimeType))) return false; return loadPlugin(renderer, completedURL, mimeType, paramNames, paramValues, useFallback); } ASSERT(renderer->node()->hasTagName(objectTag) || renderer->node()->hasTagName(embedTag)); HTMLPlugInElement* element = static_cast(renderer->node()); // FIXME: OK to always make a new frame? When does the old frame get removed? return loadSubframe(element, completedURL, frameName, m_outgoingReferrer); } bool FrameLoader::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback) { if (m_client->shouldUsePluginDocument(mimeType)) { useFallback = false; return true; } // Allow other plug-ins to win over QuickTime because if the user has installed a plug-in that // can handle TIFF (which QuickTime can also handle) they probably intended to override QT. if (m_frame->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) { const PluginData* pluginData = m_frame->page()->pluginData(); String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String(); if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false)) return true; } ObjectContentType objectType = m_client->objectContentType(url, mimeType); // If an object's content can't be handled and it has no fallback, let // it be handled as a plugin to show the broken plugin icon. useFallback = objectType == ObjectContentNone && hasFallback; return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin; } bool FrameLoader::loadPlugin(RenderPart* renderer, const KURL& url, const String& mimeType, const Vector& paramNames, const Vector& paramValues, bool useFallback) { Widget* widget = 0; if (renderer && !useFallback) { Element* pluginElement = 0; if (renderer->node() && renderer->node()->isElementNode()) pluginElement = static_cast(renderer->node()); if (!canLoad(url, String(), frame()->document())) { FrameLoader::reportLocalLoadFailed(m_frame, url.string()); return false; } widget = m_client->createPlugin(IntSize(renderer->contentWidth(), renderer->contentHeight()), pluginElement, url, paramNames, paramValues, mimeType, m_frame->document()->isPluginDocument()); if (widget) { renderer->setWidget(widget); m_containsPlugIns = true; } } return widget != 0; } void FrameLoader::clearRecordedFormValues() { m_formAboutToBeSubmitted = 0; m_formValuesAboutToBeSubmitted.clear(); } void FrameLoader::setFormAboutToBeSubmitted(PassRefPtr element) { m_formAboutToBeSubmitted = element; } void FrameLoader::recordFormValue(const String& name, const String& value) { m_formValuesAboutToBeSubmitted.set(name, value); } void FrameLoader::parentCompleted() { if (m_scheduledRedirection && !m_redirectionTimer.isActive()) startRedirectionTimer(); } String FrameLoader::outgoingReferrer() const { return m_outgoingReferrer; } String FrameLoader::outgoingOrigin() const { if (m_frame->document()) return m_frame->document()->securityOrigin()->toString(); return SecurityOrigin::createEmpty()->toString(); } Frame* FrameLoader::opener() { return m_opener; } void FrameLoader::setOpener(Frame* opener) { if (m_opener) m_opener->loader()->m_openedFrames.remove(m_frame); if (opener) opener->loader()->m_openedFrames.add(m_frame); m_opener = opener; if (m_frame->document()) { m_frame->document()->initSecurityContext(); m_frame->domWindow()->setSecurityOrigin(m_frame->document()->securityOrigin()); } } bool FrameLoader::openedByDOM() const { return m_openedByDOM; } void FrameLoader::setOpenedByDOM() { m_openedByDOM = true; } void FrameLoader::handleFallbackContent() { HTMLFrameOwnerElement* owner = m_frame->ownerElement(); if (!owner || !owner->hasTagName(objectTag)) return; static_cast(owner)->renderFallbackContent(); } void FrameLoader::provisionalLoadStarted() { #ifdef ANDROID_INSTRUMENT if (!m_frame->tree()->parent()) android::TimeCounter::reset(); #endif Page* page = m_frame->page(); // this is used to update the current history item // in the event of a navigation aytime during loading m_navigationDuringLoad = false; if (page) { Document *document = page->mainFrame()->document(); m_navigationDuringLoad = !page->mainFrame()->loader()->isComplete() || (document && document->processingLoadEvent()); } m_firstLayoutDone = false; cancelRedirection(true); m_client->provisionalLoadStarted(); } bool FrameLoader::userGestureHint() { Frame* frame = m_frame->tree()->top(); if (!frame->script()->isEnabled()) return true; // If JavaScript is disabled, a user gesture must have initiated the navigation. return frame->script()->processingUserGesture(); // FIXME: Use pageIsProcessingUserGesture. } void FrameLoader::didNotOpenURL(const KURL& url) { if (m_submittedFormURL == url) m_submittedFormURL = KURL(); } void FrameLoader::resetMultipleFormSubmissionProtection() { m_submittedFormURL = KURL(); } void FrameLoader::setEncoding(const String& name, bool userChosen) { if (!m_workingURL.isEmpty()) receivedFirstData(); m_encoding = name; m_encodingWasChosenByUser = userChosen; } void FrameLoader::addData(const char* bytes, int length) { ASSERT(m_workingURL.isEmpty()); ASSERT(m_frame->document()); ASSERT(m_frame->document()->parsing()); write(bytes, length); } bool FrameLoader::canCachePageContainingThisFrame() { return m_documentLoader && m_documentLoader->mainDocumentError().isNull() && !m_frame->tree()->childCount() // FIXME: If we ever change this so that frames with plug-ins will be cached, // we need to make sure that we don't cache frames that have outstanding NPObjects // (objects created by the plug-in). Since there is no way to pause/resume a Netscape plug-in, // they would need to be destroyed and then recreated, and there is no way that we can recreate // the right NPObjects. See for more information. && !m_containsPlugIns && !m_URL.protocolIs("https") && m_frame->document() && !m_frame->document()->hasWindowEventListener(eventNames().unloadEvent) #if ENABLE(DATABASE) && !m_frame->document()->hasOpenDatabases() #endif && !m_frame->document()->usingGeolocation() && m_currentHistoryItem && !isQuickRedirectComing() && !m_documentLoader->isLoadingInAPISense() && !m_documentLoader->isStopping() && m_frame->document()->canSuspendActiveDOMObjects() #if ENABLE(OFFLINE_WEB_APPLICATIONS) // FIXME: We should investigating caching frames that have an associated // application cache. tracks that work. && !m_documentLoader->applicationCache() && !m_documentLoader->candidateApplicationCacheGroup() #endif && m_client->canCachePage() ; } bool FrameLoader::canCachePage() { #ifndef NDEBUG logCanCachePageDecision(); #endif // Cache the page, if possible. // Don't write to the cache if in the middle of a redirect, since we will want to // store the final page we end up on. // No point writing to the cache on a reload or loadSame, since we will just write // over it again when we leave that page. // FIXME: - We should work out the complexities of caching pages with frames as they // are the most interesting pages on the web, and often those that would benefit the most from caching! FrameLoadType loadType = this->loadType(); return !m_frame->tree()->parent() && canCachePageContainingThisFrame() && m_frame->page() && m_frame->page()->backForwardList()->enabled() && m_frame->page()->backForwardList()->capacity() > 0 && m_frame->page()->settings()->usesPageCache() && loadType != FrameLoadTypeReload && loadType != FrameLoadTypeReloadFromOrigin && loadType != FrameLoadTypeSame ; } #ifndef NDEBUG static String& pageCacheLogPrefix(int indentLevel) { static int previousIndent = -1; DEFINE_STATIC_LOCAL(String, prefix, ()); if (indentLevel != previousIndent) { previousIndent = indentLevel; prefix.truncate(0); for (int i = 0; i < previousIndent; ++i) prefix += " "; } return prefix; } static void pageCacheLog(const String& prefix, const String& message) { LOG(PageCache, "%s%s", prefix.utf8().data(), message.utf8().data()); } #define PCLOG(...) pageCacheLog(pageCacheLogPrefix(indentLevel), String::format(__VA_ARGS__)) void FrameLoader::logCanCachePageDecision() { // Only bother logging for main frames that have actually loaded and have content. if (m_creatingInitialEmptyDocument) return; KURL currentURL = m_documentLoader ? m_documentLoader->url() : KURL(); if (currentURL.isEmpty()) return; int indentLevel = 0; PCLOG("--------\n Determining if page can be cached:"); bool cannotCache = !logCanCacheFrameDecision(1); FrameLoadType loadType = this->loadType(); do { if (m_frame->tree()->parent()) { PCLOG(" -Frame has a parent frame"); cannotCache = true; } if (!m_frame->page()) { PCLOG(" -There is no Page object"); cannotCache = true; break; } if (!m_frame->page()->backForwardList()->enabled()) { PCLOG(" -The back/forward list is disabled"); cannotCache = true; } if (!(m_frame->page()->backForwardList()->capacity() > 0)) { PCLOG(" -The back/forward list has a 0 capacity"); cannotCache = true; } if (!m_frame->page()->settings()->usesPageCache()) { PCLOG(" -Page settings says b/f cache disabled"); cannotCache = true; } if (loadType == FrameLoadTypeReload) { PCLOG(" -Load type is: Reload"); cannotCache = true; } if (loadType == FrameLoadTypeReloadFromOrigin) { PCLOG(" -Load type is: Reload from origin"); cannotCache = true; } if (loadType == FrameLoadTypeSame) { PCLOG(" -Load type is: Same"); cannotCache = true; } } while (false); PCLOG(cannotCache ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------"); } bool FrameLoader::logCanCacheFrameDecision(int indentLevel) { // Only bother logging for frames that have actually loaded and have content. if (m_creatingInitialEmptyDocument) return false; KURL currentURL = m_documentLoader ? m_documentLoader->url() : KURL(); if (currentURL.isEmpty()) return false; PCLOG("+---"); KURL newURL = m_provisionalDocumentLoader ? m_provisionalDocumentLoader->url() : KURL(); if (!newURL.isEmpty()) PCLOG(" Determining if frame can be cached navigating from (%s) to (%s):", currentURL.string().utf8().data(), newURL.string().utf8().data()); else PCLOG(" Determining if subframe with URL (%s) can be cached:", currentURL.string().utf8().data()); bool cannotCache = false; do { if (!m_documentLoader) { PCLOG(" -There is no DocumentLoader object"); cannotCache = true; break; } if (!m_documentLoader->mainDocumentError().isNull()) { PCLOG(" -Main document has an error"); cannotCache = true; } if (m_frame->tree()->childCount()) { PCLOG(" -Frame has child frames"); cannotCache = true; } if (m_containsPlugIns) { PCLOG(" -Frame contains plugins"); cannotCache = true; } if (m_URL.protocolIs("https")) { PCLOG(" -Frame is HTTPS"); cannotCache = true; } if (!m_frame->document()) { PCLOG(" -There is no Document object"); cannotCache = true; break; } if (m_frame->document()->hasWindowEventListener(eventNames().unloadEvent)) { PCLOG(" -Frame has an unload event listener"); cannotCache = true; } #if ENABLE(DATABASE) if (m_frame->document()->hasOpenDatabases()) { PCLOG(" -Frame has open database handles"); cannotCache = true; } #endif if (m_frame->document()->usingGeolocation()) { PCLOG(" -Frame uses Geolocation"); cannotCache = true; } if (!m_currentHistoryItem) { PCLOG(" -No current history item"); cannotCache = true; } if (isQuickRedirectComing()) { PCLOG(" -Quick redirect is coming"); cannotCache = true; } if (m_documentLoader->isLoadingInAPISense()) { PCLOG(" -DocumentLoader is still loading in API sense"); cannotCache = true; } if (m_documentLoader->isStopping()) { PCLOG(" -DocumentLoader is in the middle of stopping"); cannotCache = true; } if (!m_frame->document()->canSuspendActiveDOMObjects()) { PCLOG(" -The document cannot suspect its active DOM Objects"); cannotCache = true; } #if ENABLE(OFFLINE_WEB_APPLICATIONS) if (m_documentLoader->applicationCache()) { PCLOG(" -The DocumentLoader has an active application cache"); cannotCache = true; } if (m_documentLoader->candidateApplicationCacheGroup()) { PCLOG(" -The DocumentLoader has a candidateApplicationCacheGroup"); cannotCache = true; } #endif if (!m_client->canCachePage()) { PCLOG(" -The client says this frame cannot be cached"); cannotCache = true; } } while (false); for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) if (!child->loader()->logCanCacheFrameDecision(indentLevel + 1)) cannotCache = true; PCLOG(cannotCache ? " Frame CANNOT be cached" : " Frame CAN be cached"); PCLOG("+---"); return !cannotCache; } #endif void FrameLoader::updatePolicyBaseURL() { if (m_frame->tree()->parent() && m_frame->tree()->parent()->document()) setPolicyBaseURL(m_frame->tree()->parent()->document()->policyBaseURL()); else setPolicyBaseURL(m_URL); } void FrameLoader::setPolicyBaseURL(const KURL& url) { if (m_frame->document()) m_frame->document()->setPolicyBaseURL(url); for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) child->loader()->setPolicyBaseURL(url); } // This does the same kind of work that didOpenURL does, except it relies on the fact // that a higher level already checked that the URLs match and the scrolling is the right thing to do. void FrameLoader::scrollToAnchor(const KURL& url) { m_URL = url; updateHistoryForAnchorScroll(); // If we were in the autoscroll/panScroll mode we want to stop it before following the link to the anchor m_frame->eventHandler()->stopAutoscrollTimer(); started(); gotoAnchor(); // It's important to model this as a load that starts and immediately finishes. // Otherwise, the parent frame may think we never finished loading. m_isComplete = false; checkCompleted(); } bool FrameLoader::isComplete() const { return m_isComplete; } void FrameLoader::scheduleRedirection(ScheduledRedirection* redirection) { ASSERT(m_frame->page()); stopRedirectionTimer(); m_scheduledRedirection.set(redirection); if (!m_isComplete && redirection->type != ScheduledRedirection::redirection) completed(); if (m_isComplete || redirection->type != ScheduledRedirection::redirection) startRedirectionTimer(); } void FrameLoader::startRedirectionTimer() { ASSERT(m_frame->page()); ASSERT(m_scheduledRedirection); m_redirectionTimer.stop(); m_redirectionTimer.startOneShot(m_scheduledRedirection->delay); switch (m_scheduledRedirection->type) { case ScheduledRedirection::redirection: case ScheduledRedirection::locationChange: case ScheduledRedirection::locationChangeDuringLoad: clientRedirected(KURL(m_scheduledRedirection->url), m_scheduledRedirection->delay, currentTime() + m_redirectionTimer.nextFireInterval(), m_scheduledRedirection->lockBackForwardList, m_isExecutingJavaScriptFormAction); return; case ScheduledRedirection::historyNavigation: // Don't report history navigations. return; } ASSERT_NOT_REACHED(); } void FrameLoader::stopRedirectionTimer() { if (!m_redirectionTimer.isActive()) return; m_redirectionTimer.stop(); if (m_scheduledRedirection) { switch (m_scheduledRedirection->type) { case ScheduledRedirection::redirection: case ScheduledRedirection::locationChange: case ScheduledRedirection::locationChangeDuringLoad: clientRedirectCancelledOrFinished(m_cancellingWithLoadInProgress); return; case ScheduledRedirection::historyNavigation: // Don't report history navigations. return; } ASSERT_NOT_REACHED(); } } void FrameLoader::completed() { RefPtr