/* * 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" #include "Archive.h" #include "ArchiveFactory.h" #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 "FramePrivate.h" #include "FrameTree.h" #include "FrameView.h" #include "HTMLAnchorElement.h" #include "HTMLAppletElement.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 "SystemTime.h" #include "TextResourceDecoder.h" #include "WindowFeatures.h" #include "XMLHttpRequest.h" #include "XMLTokenizer.h" #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 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 { const char* action; String url; RefPtr data; String target; String contentType; String boundary; RefPtr event; FormSubmission(const char* a, const String& u, PassRefPtr d, const String& t, const String& ct, const String& b, PassRefPtr e) : action(a) , url(u) , data(d) , target(t) , contentType(ct) , boundary(b) , event(e) { } }; struct ScheduledRedirection { enum Type { redirection, locationChange, historyNavigation, locationChangeDuringLoad }; Type type; double delay; String url; String referrer; int historySteps; bool lockHistory; bool wasUserGesture; bool wasRefresh; ScheduledRedirection(double redirectDelay, const String& redirectURL, bool redirectLockHistory, bool userGesture, bool refresh) : type(redirection) , delay(redirectDelay) , url(redirectURL) , historySteps(0) , lockHistory(redirectLockHistory) , wasUserGesture(userGesture) , wasRefresh(refresh) { } ScheduledRedirection(Type locationChangeType, const String& locationChangeURL, const String& locationChangeReferrer, bool locationChangeLockHistory, bool locationChangeWasUserGesture, bool refresh) : type(locationChangeType) , delay(0) , url(locationChangeURL) , referrer(locationChangeReferrer) , historySteps(0) , lockHistory(locationChangeLockHistory) , wasUserGesture(locationChangeWasUserGesture) , 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 FrameLoadTypeReloadAllowingStaleData: case FrameLoadTypeReloadFromOrigin: case FrameLoadTypeSame: case FrameLoadTypeRedirectWithLockedHistory: 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_unloadEventBeingDispatched(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(String("")), 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, 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 userGesture, bool refresh) { changeLocation(completeURL(url), referrer, lockHistory, userGesture, refresh); } void FrameLoader::changeLocation(const KURL& url, const String& referrer, bool lockHistory, bool userGesture, bool refresh) { RefPtr protect(m_frame); ResourceRequest request(url, referrer, refresh ? ReloadIgnoringCacheData : UseProtocolCachePolicy); if (executeIfJavaScriptURL(request.url(), userGesture)) return; urlSelected(request, "_self", 0, lockHistory, userGesture); } void FrameLoader::urlSelected(const FrameLoadRequest& request, Event* event, bool lockHistory) { FrameLoadRequest copy = request; if (copy.resourceRequest().httpReferrer().isEmpty()) copy.resourceRequest().setHTTPReferrer(m_outgoingReferrer); addHTTPOriginIfNeeded(copy.resourceRequest(), outgoingOrigin()); loadFrameRequestWithFormAndValues(copy, lockHistory, event, 0, HashMap()); } void FrameLoader::urlSelected(const ResourceRequest& request, const String& _target, Event* triggeringEvent, bool lockHistory, 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); urlSelected(frameRequest, triggeringEvent, lockHistory); } 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 = KURL(urlString); url = blankURL(); } else url = completeURL(urlString); Frame* frame = ownerElement->contentFrame(); if (frame) frame->loader()->scheduleLocationChange(url.string(), m_outgoingReferrer, 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) submitForm(form->action, form->url, form->data, form->target, form->contentType, form->boundary, form->event.get()); } void FrameLoader::submitForm(const char* action, const String& url, PassRefPtr formData, const String& target, const String& contentType, const String& boundary, Event* event) { 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)); return; } formData->generateFiles(m_frame->page()->chrome()->client()); FrameLoadRequest frameRequest; 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); } 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_unloadEventBeingDispatched = true; m_frame->document()->dispatchWindowEvent(eventNames().unloadEvent, false, false); m_unloadEventBeingDispatched = 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()); 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 FrameLoadTypeReloadAllowingStaleData: case FrameLoadTypeReloadFromOrigin: case FrameLoadTypeSame: case FrameLoadTypeReplace: break; case FrameLoadTypeBack: case FrameLoadTypeForward: case FrameLoadTypeIndexedBackForward: case FrameLoadTypeRedirectWithLockedHistory: case FrameLoadTypeStandard: itemToRestore = m_currentHistoryItem.get(); } if (!itemToRestore) return; 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) scheduleRedirection(new ScheduledRedirection(delay, url, delay <= 1, false, false)); } void FrameLoader::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, 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, 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, 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, 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->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->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 = FrameLoadTypeRedirectWithLockedHistory; 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. // Reload will maintain the frame contents, LoadSame will not. if (parentItem && parentItem->children().size() && (isBackForwardLoadType(loadType) || loadType == FrameLoadTypeReloadAllowingStaleData)) { 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()); // These behaviors implied by these loadTypes should apply to the child frames childLoadType = loadType; if (isBackForwardLoadType(loadType)) { // For back/forward, remember this item so we can traverse any child items as child frames load childFrame->loader()->setProvisionalHistoryItem(childItem); } else { // For reload, just reinstall the current item, since a new child frame was created but we won't be creating a new BF item childFrame->loader()->setCurrentHistoryItem(childItem); } } } RefPtr subframeArchive = activeDocumentLoader()->popArchiveForSubframe(childFrame->tree()->name()); if (subframeArchive) childFrame->loader()->loadArchive(subframeArchive.release()); else childFrame->loader()->loadURL(workingURL, referer, String(), childLoadType, 0, 0); } 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()); } 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(); } if (renderer) renderer->enclosingLayer()->scrollRectToVisible(rect, true, RenderLayer::gAlignToEdgeIfNeeded, RenderLayer::gAlignTopAlways); 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() { 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* rootFrame = m_frame; while (rootFrame->tree()->parent()) rootFrame = rootFrame->tree()->parent(); if (rootFrame->script()->isEnabled()) return rootFrame->script()->processingUserGesture(); return true; // If JavaScript is disabled, a user gesture must have initiated the navigation } 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::canCachePage() { // 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_documentLoader && m_documentLoader->mainDocumentError().isNull() && !m_frame->tree()->childCount() && !m_frame->tree()->parent() // FIXME: If we ever change this so that pages with plug-ins will be cached, // we need to make sure that we don't cache pages 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_frame->page() && m_frame->page()->backForwardList()->enabled() && m_frame->page()->backForwardList()->capacity() > 0 && m_frame->page()->settings()->usesPageCache() && m_currentHistoryItem && !isQuickRedirectComing() && loadType != FrameLoadTypeReload && loadType != FrameLoadTypeReloadAllowingStaleData && loadType != FrameLoadTypeReloadFromOrigin && loadType != FrameLoadTypeSame && !m_documentLoader->isLoadingInAPISense() && !m_documentLoader->isStopping() && m_frame->document()->canSuspendActiveDOMObjects() #if ENABLE(OFFLINE_WEB_APPLICATIONS) // FIXME: We should investigating caching pages that have an associated // application cache. tracks that work. && !m_documentLoader->applicationCache() && !m_documentLoader->candidateApplicationCacheGroup() #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->lockHistory, 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