| /* |
| * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 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 "core/loader/DocumentLoader.h" |
| |
| #include <memory> |
| #include "core/dom/Document.h" |
| #include "core/dom/DocumentParser.h" |
| #include "core/dom/WeakIdentifierMap.h" |
| #include "core/events/Event.h" |
| #include "core/frame/Deprecation.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameClient.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/parser/HTMLParserIdioms.h" |
| #include "core/html/parser/TextResourceDecoder.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/inspector/MainThreadDebugger.h" |
| #include "core/loader/FrameFetchContext.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/loader/LinkLoader.h" |
| #include "core/loader/NetworkHintsInterface.h" |
| #include "core/loader/ProgressTracker.h" |
| #include "core/loader/SubresourceFilter.h" |
| #include "core/loader/appcache/ApplicationCacheHost.h" |
| #include "core/loader/resource/CSSStyleSheetResource.h" |
| #include "core/loader/resource/FontResource.h" |
| #include "core/loader/resource/ImageResource.h" |
| #include "core/loader/resource/ScriptResource.h" |
| #include "core/origin_trials/OriginTrialContext.h" |
| #include "core/page/FrameTree.h" |
| #include "core/page/Page.h" |
| #include "core/probe/CoreProbes.h" |
| #include "core/timing/DOMWindowPerformance.h" |
| #include "core/timing/Performance.h" |
| #include "platform/HTTPNames.h" |
| #include "platform/UserGestureIndicator.h" |
| #include "platform/feature_policy/FeaturePolicy.h" |
| #include "platform/loader/fetch/FetchInitiatorTypeNames.h" |
| #include "platform/loader/fetch/FetchParameters.h" |
| #include "platform/loader/fetch/FetchUtils.h" |
| #include "platform/loader/fetch/MemoryCache.h" |
| #include "platform/loader/fetch/ResourceFetcher.h" |
| #include "platform/loader/fetch/ResourceTimingInfo.h" |
| #include "platform/mhtml/ArchiveResource.h" |
| #include "platform/network/ContentSecurityPolicyResponseHeaders.h" |
| #include "platform/network/HTTPParsers.h" |
| #include "platform/network/mime/MIMETypeRegistry.h" |
| #include "platform/plugins/PluginData.h" |
| #include "platform/weborigin/SchemeRegistry.h" |
| #include "platform/weborigin/SecurityPolicy.h" |
| #include "platform/wtf/Assertions.h" |
| #include "platform/wtf/AutoReset.h" |
| #include "platform/wtf/text/WTFString.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/modules/serviceworker/WebServiceWorkerNetworkProvider.h" |
| |
| namespace blink { |
| |
| static bool IsArchiveMIMEType(const String& mime_type) { |
| return DeprecatedEqualIgnoringCase("multipart/related", mime_type); |
| } |
| |
| DocumentLoader::DocumentLoader(LocalFrame* frame, |
| const ResourceRequest& req, |
| const SubstituteData& substitute_data, |
| ClientRedirectPolicy client_redirect_policy) |
| : frame_(frame), |
| fetcher_(FrameFetchContext::CreateFetcherFromDocumentLoader(this)), |
| original_request_(req), |
| substitute_data_(substitute_data), |
| request_(req), |
| load_type_(kFrameLoadTypeStandard), |
| is_client_redirect_(client_redirect_policy == |
| ClientRedirectPolicy::kClientRedirect), |
| replaces_current_history_item_(false), |
| data_received_(false), |
| navigation_type_(kNavigationTypeOther), |
| document_load_timing_(*this), |
| time_of_last_data_received_(0.0), |
| application_cache_host_(ApplicationCacheHost::Create(this)), |
| was_blocked_after_csp_(false), |
| state_(kNotStarted), |
| in_data_received_(false), |
| data_buffer_(SharedBuffer::Create()) { |
| DCHECK(frame_); |
| |
| // The document URL needs to be added to the head of the list as that is |
| // where the redirects originated. |
| if (is_client_redirect_) |
| AppendRedirect(frame_->GetDocument()->Url()); |
| } |
| |
| FrameLoader& DocumentLoader::GetFrameLoader() const { |
| DCHECK(frame_); |
| return frame_->Loader(); |
| } |
| |
| LocalFrameClient& DocumentLoader::GetLocalFrameClient() const { |
| DCHECK(frame_); |
| LocalFrameClient* client = frame_->Client(); |
| // LocalFrame clears its |m_client| only after detaching all DocumentLoaders |
| // (i.e. calls detachFromFrame() which clears |frame_|) owned by the |
| // LocalFrame's FrameLoader. So, if |frame_| is non nullptr, |client| is |
| // also non nullptr. |
| DCHECK(client); |
| return *client; |
| } |
| |
| DocumentLoader::~DocumentLoader() { |
| DCHECK(!frame_); |
| DCHECK(!main_resource_); |
| DCHECK(!application_cache_host_); |
| DCHECK_EQ(state_, kSentDidFinishLoad); |
| } |
| |
| DEFINE_TRACE(DocumentLoader) { |
| visitor->Trace(frame_); |
| visitor->Trace(fetcher_); |
| visitor->Trace(main_resource_); |
| visitor->Trace(history_item_); |
| visitor->Trace(writer_); |
| visitor->Trace(subresource_filter_); |
| visitor->Trace(document_load_timing_); |
| visitor->Trace(application_cache_host_); |
| visitor->Trace(content_security_policy_); |
| RawResourceClient::Trace(visitor); |
| } |
| |
| unsigned long DocumentLoader::MainResourceIdentifier() const { |
| return main_resource_ ? main_resource_->Identifier() : 0; |
| } |
| |
| ResourceTimingInfo* DocumentLoader::GetNavigationTimingInfo() const { |
| DCHECK(Fetcher()); |
| return Fetcher()->GetNavigationTimingInfo(); |
| } |
| |
| const ResourceRequest& DocumentLoader::OriginalRequest() const { |
| return original_request_; |
| } |
| |
| const ResourceRequest& DocumentLoader::GetRequest() const { |
| return request_; |
| } |
| |
| void DocumentLoader::SetSubresourceFilter( |
| SubresourceFilter* subresource_filter) { |
| subresource_filter_ = subresource_filter; |
| } |
| |
| const KURL& DocumentLoader::Url() const { |
| return request_.Url(); |
| } |
| |
| Resource* DocumentLoader::StartPreload(Resource::Type type, |
| FetchParameters& params) { |
| Resource* resource = nullptr; |
| switch (type) { |
| case Resource::kImage: |
| if (frame_) |
| frame_->MaybeAllowImagePlaceholder(params); |
| resource = ImageResource::Fetch(params, Fetcher()); |
| break; |
| case Resource::kScript: |
| resource = ScriptResource::Fetch(params, Fetcher()); |
| break; |
| case Resource::kCSSStyleSheet: |
| resource = CSSStyleSheetResource::Fetch(params, Fetcher()); |
| break; |
| case Resource::kFont: |
| resource = FontResource::Fetch(params, Fetcher()); |
| break; |
| case Resource::kMedia: |
| resource = RawResource::FetchMedia(params, Fetcher()); |
| break; |
| case Resource::kTextTrack: |
| resource = RawResource::FetchTextTrack(params, Fetcher()); |
| break; |
| case Resource::kImportResource: |
| resource = RawResource::FetchImport(params, Fetcher()); |
| break; |
| case Resource::kRaw: |
| resource = RawResource::Fetch(params, Fetcher()); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // CSP layout tests verify that preloads are subject to access checks by |
| // seeing if they are in the `preload started` list. Therefore do not add |
| // them to the list if the load is immediately denied. |
| if (resource && !resource->GetResourceError().IsAccessCheck()) |
| Fetcher()->PreloadStarted(resource); |
| return resource; |
| } |
| |
| void DocumentLoader::SetServiceWorkerNetworkProvider( |
| std::unique_ptr<WebServiceWorkerNetworkProvider> provider) { |
| service_worker_network_provider_ = std::move(provider); |
| } |
| |
| void DocumentLoader::SetSourceLocation( |
| std::unique_ptr<SourceLocation> source_location) { |
| source_location_ = std::move(source_location); |
| } |
| |
| std::unique_ptr<SourceLocation> DocumentLoader::CopySourceLocation() const { |
| return source_location_ ? source_location_->Clone() : nullptr; |
| } |
| |
| void DocumentLoader::DispatchLinkHeaderPreloads( |
| ViewportDescriptionWrapper* viewport, |
| LinkLoader::MediaPreloadPolicy media_policy) { |
| DCHECK_GE(state_, kCommitted); |
| LinkLoader::LoadLinksFromHeader( |
| GetResponse().HttpHeaderField(HTTPNames::Link), GetResponse().Url(), |
| *frame_, frame_->GetDocument(), NetworkHintsInterfaceImpl(), |
| LinkLoader::kOnlyLoadResources, media_policy, viewport); |
| } |
| |
| void DocumentLoader::DidChangePerformanceTiming() { |
| if (frame_ && state_ >= kCommitted) { |
| GetLocalFrameClient().DidChangePerformanceTiming(); |
| } |
| } |
| |
| void DocumentLoader::DidObserveLoadingBehavior( |
| WebLoadingBehaviorFlag behavior) { |
| if (frame_) { |
| DCHECK_GE(state_, kCommitted); |
| GetLocalFrameClient().DidObserveLoadingBehavior(behavior); |
| } |
| } |
| |
| void DocumentLoader::MarkAsCommitted() { |
| DCHECK_LT(state_, kCommitted); |
| state_ = kCommitted; |
| } |
| |
| static HistoryCommitType LoadTypeToCommitType(FrameLoadType type) { |
| switch (type) { |
| case kFrameLoadTypeStandard: |
| return kStandardCommit; |
| case kFrameLoadTypeInitialInChildFrame: |
| case kFrameLoadTypeInitialHistoryLoad: |
| return kInitialCommitInChildFrame; |
| case kFrameLoadTypeBackForward: |
| return kBackForwardCommit; |
| default: |
| break; |
| } |
| return kHistoryInertCommit; |
| } |
| |
| void DocumentLoader::UpdateForSameDocumentNavigation( |
| const KURL& new_url, |
| SameDocumentNavigationSource same_document_navigation_source, |
| PassRefPtr<SerializedScriptValue> data, |
| HistoryScrollRestorationType scroll_restoration_type, |
| FrameLoadType type, |
| Document* initiating_document) { |
| if (initiating_document && !initiating_document->CanCreateHistoryEntry()) |
| type = kFrameLoadTypeReplaceCurrentItem; |
| |
| KURL old_url = request_.Url(); |
| original_request_.SetURL(new_url); |
| request_.SetURL(new_url); |
| SetReplacesCurrentHistoryItem(type != kFrameLoadTypeStandard); |
| if (same_document_navigation_source == kSameDocumentNavigationHistoryApi) { |
| request_.SetHTTPMethod(HTTPNames::GET); |
| request_.SetHTTPBody(nullptr); |
| } |
| ClearRedirectChain(); |
| if (is_client_redirect_) |
| AppendRedirect(old_url); |
| AppendRedirect(new_url); |
| |
| SetHistoryItemStateForCommit( |
| history_item_.Get(), type, |
| same_document_navigation_source == kSameDocumentNavigationHistoryApi |
| ? HistoryNavigationType::kHistoryApi |
| : HistoryNavigationType::kFragment); |
| history_item_->SetDocumentState(frame_->GetDocument()->FormElementsState()); |
| if (same_document_navigation_source == kSameDocumentNavigationHistoryApi) { |
| history_item_->SetStateObject(std::move(data)); |
| history_item_->SetScrollRestorationType(scroll_restoration_type); |
| } |
| GetLocalFrameClient().DispatchDidNavigateWithinPage( |
| history_item_.Get(), LoadTypeToCommitType(type), initiating_document); |
| } |
| |
| const KURL& DocumentLoader::UrlForHistory() const { |
| return UnreachableURL().IsEmpty() ? Url() : UnreachableURL(); |
| } |
| |
| void DocumentLoader::SetHistoryItemStateForCommit( |
| HistoryItem* old_item, |
| FrameLoadType load_type, |
| HistoryNavigationType navigation_type) { |
| if (!history_item_ || !IsBackForwardLoadType(load_type)) |
| history_item_ = HistoryItem::Create(); |
| |
| history_item_->SetURL(UrlForHistory()); |
| history_item_->SetReferrer(SecurityPolicy::GenerateReferrer( |
| request_.GetReferrerPolicy(), history_item_->Url(), |
| request_.HttpReferrer())); |
| history_item_->SetFormInfoFromRequest(request_); |
| |
| // Don't propagate state from the old item to the new item if there isn't an |
| // old item (obviously), or if this is a back/forward navigation, since we |
| // explicitly want to restore the state we just committed. |
| if (!old_item || IsBackForwardLoadType(load_type)) |
| return; |
| // Don't propagate state from the old item if this is a different-document |
| // navigation, unless the before and after pages are logically related. This |
| // means they have the same url (ignoring fragment) and the new item was |
| // loaded via reload or client redirect. |
| HistoryCommitType history_commit_type = LoadTypeToCommitType(load_type); |
| if (navigation_type == HistoryNavigationType::kDifferentDocument && |
| (history_commit_type != kHistoryInertCommit || |
| !EqualIgnoringFragmentIdentifier(old_item->Url(), history_item_->Url()))) |
| return; |
| history_item_->SetDocumentSequenceNumber(old_item->DocumentSequenceNumber()); |
| history_item_->SetScrollOffset(old_item->GetScrollOffset()); |
| history_item_->SetDidSaveScrollOrScaleState( |
| old_item->DidSaveScrollOrScaleState()); |
| history_item_->SetVisualViewportScrollOffset( |
| old_item->VisualViewportScrollOffset()); |
| history_item_->SetPageScaleFactor(old_item->PageScaleFactor()); |
| history_item_->SetScrollRestorationType(old_item->ScrollRestorationType()); |
| |
| // The item sequence number determines whether items are "the same", such |
| // back/forward navigation between items with the same item sequence number is |
| // a no-op. Only treat this as identical if the navigation did not create a |
| // back/forward entry and the url is identical or it was loaded via |
| // history.replaceState(). |
| if (history_commit_type == kHistoryInertCommit && |
| (navigation_type == HistoryNavigationType::kHistoryApi || |
| old_item->Url() == history_item_->Url())) { |
| history_item_->SetStateObject(old_item->StateObject()); |
| history_item_->SetItemSequenceNumber(old_item->ItemSequenceNumber()); |
| } |
| } |
| |
| void DocumentLoader::NotifyFinished(Resource* resource) { |
| DCHECK_EQ(main_resource_, resource); |
| DCHECK(main_resource_); |
| |
| if (!main_resource_->ErrorOccurred() && !main_resource_->WasCanceled()) { |
| FinishedLoading(main_resource_->LoadFinishTime()); |
| return; |
| } |
| |
| if (application_cache_host_) |
| application_cache_host_->FailedLoadingMainResource(); |
| |
| if (main_resource_->GetResourceError().WasBlockedByResponse()) { |
| probe::CanceledAfterReceivedResourceResponse( |
| frame_, this, MainResourceIdentifier(), resource->GetResponse(), |
| main_resource_.Get()); |
| } |
| |
| LoadFailed(main_resource_->GetResourceError()); |
| ClearMainResourceHandle(); |
| } |
| |
| void DocumentLoader::LoadFailed(const ResourceError& error) { |
| if (!error.IsCancellation() && frame_->Owner()) { |
| // FIXME: For now, fallback content doesn't work cross process. |
| if (frame_->Owner()->IsLocal()) |
| frame_->DeprecatedLocalOwner()->RenderFallbackContent(); |
| } |
| |
| HistoryCommitType history_commit_type = LoadTypeToCommitType(load_type_); |
| switch (state_) { |
| case kNotStarted: |
| probe::frameClearedScheduledClientNavigation(frame_); |
| // Fall-through |
| case kProvisional: |
| state_ = kSentDidFinishLoad; |
| GetLocalFrameClient().DispatchDidFailProvisionalLoad(error, |
| history_commit_type); |
| if (frame_) |
| GetFrameLoader().DetachProvisionalDocumentLoader(this); |
| break; |
| case kCommitted: |
| if (frame_->GetDocument()->Parser()) |
| frame_->GetDocument()->Parser()->StopParsing(); |
| state_ = kSentDidFinishLoad; |
| GetLocalFrameClient().DispatchDidFailLoad(error, history_commit_type); |
| if (frame_) |
| frame_->GetDocument()->CheckCompleted(); |
| break; |
| case kSentDidFinishLoad: |
| // TODO(japhet): Why do we need to call DidFinishNavigation() again? |
| GetFrameLoader().DidFinishNavigation(); |
| break; |
| } |
| DCHECK_EQ(kSentDidFinishLoad, state_); |
| } |
| |
| void DocumentLoader::FinishedLoading(double finish_time) { |
| DCHECK(frame_->Loader().StateMachine()->CreatingInitialEmptyDocument() || |
| !frame_->GetPage()->Suspended() || |
| MainThreadDebugger::Instance()->IsPaused()); |
| |
| double response_end_time = finish_time; |
| if (!response_end_time) |
| response_end_time = time_of_last_data_received_; |
| if (!response_end_time) |
| response_end_time = MonotonicallyIncreasingTime(); |
| GetTiming().SetResponseEnd(response_end_time); |
| if (!MaybeCreateArchive()) { |
| // If this is an empty document, it will not have actually been created yet. |
| // Commit dummy data so that DocumentWriter::begin() gets called and creates |
| // the Document. |
| if (!writer_) |
| CommitData(0, 0); |
| } |
| |
| if (!frame_) |
| return; |
| |
| application_cache_host_->FinishedLoadingMainResource(); |
| EndWriting(); |
| ClearMainResourceHandle(); |
| } |
| |
| bool DocumentLoader::RedirectReceived( |
| Resource* resource, |
| const ResourceRequest& request, |
| const ResourceResponse& redirect_response) { |
| DCHECK(frame_); |
| DCHECK_EQ(resource, main_resource_); |
| DCHECK(!redirect_response.IsNull()); |
| request_ = request; |
| |
| // If the redirecting url is not allowed to display content from the target |
| // origin, then block the redirect. |
| const KURL& request_url = request_.Url(); |
| RefPtr<SecurityOrigin> redirecting_origin = |
| SecurityOrigin::Create(redirect_response.Url()); |
| if (!redirecting_origin->CanDisplay(request_url)) { |
| FrameLoader::ReportLocalLoadFailed(frame_, request_url.GetString()); |
| fetcher_->StopFetching(); |
| return false; |
| } |
| if (GetFrameLoader().ShouldContinueForRedirectNavigationPolicy( |
| request_, SubstituteData(), this, kCheckContentSecurityPolicy, |
| navigation_type_, kNavigationPolicyCurrentTab, load_type_, |
| IsClientRedirect(), nullptr) != kNavigationPolicyCurrentTab) { |
| fetcher_->StopFetching(); |
| return false; |
| } |
| |
| DCHECK(GetTiming().FetchStart()); |
| AppendRedirect(request_url); |
| GetTiming().AddRedirect(redirect_response.Url(), request_url); |
| |
| // If a redirection happens during a back/forward navigation, don't restore |
| // any state from the old HistoryItem. There is a provisional history item for |
| // back/forward navigation only. In the other case, clearing it is a no-op. |
| history_item_.Clear(); |
| |
| GetLocalFrameClient().DispatchDidReceiveServerRedirectForProvisionalLoad(); |
| |
| return true; |
| } |
| |
| static bool CanShowMIMEType(const String& mime_type, LocalFrame* frame) { |
| if (MIMETypeRegistry::IsSupportedMIMEType(mime_type)) |
| return true; |
| PluginData* plugin_data = frame->GetPluginData(); |
| return !mime_type.IsEmpty() && plugin_data && |
| plugin_data->SupportsMimeType(mime_type); |
| } |
| |
| bool DocumentLoader::ShouldContinueForResponse() const { |
| if (substitute_data_.IsValid()) |
| return true; |
| |
| int status_code = response_.HttpStatusCode(); |
| if (status_code == 204 || status_code == 205) { |
| // The server does not want us to replace the page contents. |
| return false; |
| } |
| |
| if (IsContentDispositionAttachment( |
| response_.HttpHeaderField(HTTPNames::Content_Disposition))) { |
| // The server wants us to download instead of replacing the page contents. |
| // Downloading is handled by the embedder, but we still get the initial |
| // response so that we can ignore it and clean up properly. |
| return false; |
| } |
| |
| if (!CanShowMIMEType(response_.MimeType(), frame_)) |
| return false; |
| return true; |
| } |
| |
| void DocumentLoader::CancelLoadAfterCSPDenied( |
| const ResourceResponse& response) { |
| probe::CanceledAfterReceivedResourceResponse( |
| frame_, this, MainResourceIdentifier(), response, main_resource_.Get()); |
| |
| SetWasBlockedAfterCSP(); |
| |
| // Pretend that this was an empty HTTP 200 response. Don't reuse the original |
| // URL for the empty page (https://crbug.com/622385). |
| // |
| // TODO(mkwst): Remove this once XFO moves to the browser. |
| // https://crbug.com/555418. |
| ClearMainResourceHandle(); |
| content_security_policy_.Clear(); |
| KURL blocked_url = SecurityOrigin::UrlWithUniqueSecurityOrigin(); |
| original_request_.SetURL(blocked_url); |
| request_.SetURL(blocked_url); |
| redirect_chain_.pop_back(); |
| AppendRedirect(blocked_url); |
| response_ = ResourceResponse(blocked_url, "text/html", 0, g_null_atom); |
| FinishedLoading(MonotonicallyIncreasingTime()); |
| |
| return; |
| } |
| |
| void DocumentLoader::ResponseReceived( |
| Resource* resource, |
| const ResourceResponse& response, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| DCHECK_EQ(main_resource_, resource); |
| DCHECK(!handle); |
| DCHECK(frame_); |
| |
| application_cache_host_->DidReceiveResponseForMainResource(response); |
| |
| // The memory cache doesn't understand the application cache or its caching |
| // rules. So if a main resource is served from the application cache, ensure |
| // we don't save the result for future use. All responses loaded from appcache |
| // will have a non-zero appCacheID(). |
| if (response.AppCacheID()) |
| GetMemoryCache()->Remove(main_resource_.Get()); |
| |
| content_security_policy_ = ContentSecurityPolicy::Create(); |
| content_security_policy_->SetOverrideURLForSelf(response.Url()); |
| content_security_policy_->DidReceiveHeaders( |
| ContentSecurityPolicyResponseHeaders(response)); |
| if (!content_security_policy_->AllowAncestors(frame_, response.Url())) { |
| CancelLoadAfterCSPDenied(response); |
| return; |
| } |
| |
| if (RuntimeEnabledFeatures::embedderCSPEnforcementEnabled() && |
| !GetFrameLoader().RequiredCSP().IsEmpty()) { |
| SecurityOrigin* parent_security_origin = |
| frame_->Tree().Parent()->GetSecurityContext()->GetSecurityOrigin(); |
| if (ContentSecurityPolicy::ShouldEnforceEmbeddersPolicy( |
| response, parent_security_origin)) { |
| content_security_policy_->AddPolicyFromHeaderValue( |
| GetFrameLoader().RequiredCSP(), |
| kContentSecurityPolicyHeaderTypeEnforce, |
| kContentSecurityPolicyHeaderSourceHTTP); |
| } else { |
| ContentSecurityPolicy* embedding_csp = ContentSecurityPolicy::Create(); |
| embedding_csp->AddPolicyFromHeaderValue( |
| GetFrameLoader().RequiredCSP(), |
| kContentSecurityPolicyHeaderTypeEnforce, |
| kContentSecurityPolicyHeaderSourceHTTP); |
| if (!embedding_csp->Subsumes(*content_security_policy_)) { |
| String message = "Refused to display '" + |
| response.Url().ElidedString() + |
| "' because it has not opted-into the following policy " |
| "required by its embedder: '" + |
| GetFrameLoader().RequiredCSP() + "'."; |
| ConsoleMessage* console_message = ConsoleMessage::CreateForRequest( |
| kSecurityMessageSource, kErrorMessageLevel, message, response.Url(), |
| MainResourceIdentifier()); |
| frame_->GetDocument()->AddConsoleMessage(console_message); |
| CancelLoadAfterCSPDenied(response); |
| return; |
| } |
| } |
| } |
| |
| DCHECK(!frame_->GetPage()->Suspended()); |
| |
| if (response.DidServiceWorkerNavigationPreload()) |
| UseCounter::Count(frame_, UseCounter::kServiceWorkerNavigationPreload); |
| response_ = response; |
| |
| if (IsArchiveMIMEType(response_.MimeType()) && |
| main_resource_->GetDataBufferingPolicy() != kBufferData) |
| main_resource_->SetDataBufferingPolicy(kBufferData); |
| |
| if (!ShouldContinueForResponse()) { |
| probe::ContinueWithPolicyIgnore(frame_, this, main_resource_->Identifier(), |
| response_, main_resource_.Get()); |
| fetcher_->StopFetching(); |
| return; |
| } |
| |
| if (frame_->Owner() && response_.IsHTTP() && |
| !FetchUtils::IsOkStatus(response_.HttpStatusCode())) |
| frame_->Owner()->RenderFallbackContent(); |
| } |
| |
| void DocumentLoader::EnsureWriter(const AtomicString& mime_type, |
| const KURL& overriding_url) { |
| if (writer_) |
| return; |
| |
| // Set history state before commitProvisionalLoad() so that we still have |
| // access to the previous committed DocumentLoader's HistoryItem, in case we |
| // need to copy state from it. |
| if (!GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) { |
| SetHistoryItemStateForCommit( |
| GetFrameLoader().GetDocumentLoader()->GetHistoryItem(), load_type_, |
| HistoryNavigationType::kDifferentDocument); |
| } |
| |
| DCHECK_EQ(state_, kProvisional); |
| GetFrameLoader().CommitProvisionalLoad(); |
| if (!frame_) |
| return; |
| |
| const AtomicString& encoding = GetResponse().TextEncodingName(); |
| |
| // Prepare a DocumentInit before clearing the frame, because it may need to |
| // inherit an aliased security context. |
| Document* owner = nullptr; |
| // TODO(dcheng): This differs from the behavior of both IE and Firefox: the |
| // origin is inherited from the document that loaded the URL. |
| if (Document::ShouldInheritSecurityOriginFromOwner(Url())) { |
| Frame* owner_frame = frame_->Tree().Parent(); |
| if (!owner_frame) |
| owner_frame = frame_->Loader().Opener(); |
| if (owner_frame && owner_frame->IsLocalFrame()) |
| owner = ToLocalFrame(owner_frame)->GetDocument(); |
| } |
| DocumentInit init(owner, Url(), frame_); |
| init.WithNewRegistrationContext(); |
| frame_->Loader().Clear(); |
| DCHECK(frame_->GetPage()); |
| |
| ParserSynchronizationPolicy parsing_policy = kAllowAsynchronousParsing; |
| if ((substitute_data_.IsValid() && substitute_data_.ForceSynchronousLoad()) || |
| !Document::ThreadedParsingEnabledForTesting()) |
| parsing_policy = kForceSynchronousParsing; |
| |
| InstallNewDocument(init, mime_type, encoding, |
| InstallNewDocumentReason::kNavigation, parsing_policy, |
| overriding_url); |
| writer_->SetDocumentWasLoadedAsPartOfNavigation(); |
| frame_->GetDocument()->MaybeHandleHttpRefresh( |
| response_.HttpHeaderField(HTTPNames::Refresh), |
| Document::kHttpRefreshFromHeader); |
| } |
| |
| void DocumentLoader::CommitData(const char* bytes, size_t length) { |
| EnsureWriter(response_.MimeType()); |
| DCHECK_GE(state_, kCommitted); |
| |
| // This can happen if document.close() is called by an event handler while |
| // there's still pending incoming data. |
| if (!frame_ || !frame_->GetDocument()->Parsing()) |
| return; |
| |
| if (length) |
| data_received_ = true; |
| |
| writer_->AddData(bytes, length); |
| } |
| |
| void DocumentLoader::DataReceived(Resource* resource, |
| const char* data, |
| size_t length) { |
| DCHECK(data); |
| DCHECK(length); |
| DCHECK_EQ(resource, main_resource_); |
| DCHECK(!response_.IsNull()); |
| DCHECK(!frame_->GetPage()->Suspended()); |
| |
| if (in_data_received_) { |
| // If this function is reentered, defer processing of the additional data to |
| // the top-level invocation. Reentrant calls can occur because of web |
| // platform (mis-)features that require running a nested run loop: |
| // - alert(), confirm(), prompt() |
| // - Detach of plugin elements. |
| // - Synchronous XMLHTTPRequest |
| data_buffer_->Append(data, length); |
| return; |
| } |
| |
| AutoReset<bool> reentrancy_protector(&in_data_received_, true); |
| ProcessData(data, length); |
| |
| // Process data received in reentrant invocations. Note that the invocations |
| // of processData() may queue more data in reentrant invocations, so iterate |
| // until it's empty. |
| const char* segment; |
| size_t pos = 0; |
| while (size_t length = data_buffer_->GetSomeData(segment, pos)) { |
| ProcessData(segment, length); |
| pos += length; |
| } |
| // All data has been consumed, so flush the buffer. |
| data_buffer_->Clear(); |
| } |
| |
| void DocumentLoader::ProcessData(const char* data, size_t length) { |
| application_cache_host_->MainResourceDataReceived(data, length); |
| time_of_last_data_received_ = MonotonicallyIncreasingTime(); |
| |
| if (IsArchiveMIMEType(GetResponse().MimeType())) |
| return; |
| CommitData(data, length); |
| |
| // If we are sending data to MediaDocument, we should stop here and cancel the |
| // request. |
| if (frame_ && frame_->GetDocument()->IsMediaDocument()) |
| fetcher_->StopFetching(); |
| } |
| |
| void DocumentLoader::ClearRedirectChain() { |
| redirect_chain_.clear(); |
| } |
| |
| void DocumentLoader::AppendRedirect(const KURL& url) { |
| redirect_chain_.push_back(url); |
| } |
| |
| void DocumentLoader::DetachFromFrame() { |
| DCHECK(frame_); |
| |
| // It never makes sense to have a document loader that is detached from its |
| // frame have any loads active, so go ahead and kill all the loads. |
| fetcher_->StopFetching(); |
| |
| if (frame_ && !SentDidFinishLoad()) |
| LoadFailed(ResourceError::CancelledError(Url())); |
| |
| // If that load cancellation triggered another detach, leave. |
| // (fast/frames/detach-frame-nested-no-crash.html is an example of this.) |
| if (!frame_) |
| return; |
| |
| fetcher_->ClearContext(); |
| application_cache_host_->DetachFromDocumentLoader(); |
| application_cache_host_.Clear(); |
| service_worker_network_provider_ = nullptr; |
| WeakIdentifierMap<DocumentLoader>::NotifyObjectDestroyed(this); |
| ClearMainResourceHandle(); |
| frame_ = nullptr; |
| } |
| |
| void DocumentLoader::ClearMainResourceHandle() { |
| if (!main_resource_) |
| return; |
| main_resource_->RemoveClient(this); |
| main_resource_ = nullptr; |
| } |
| |
| bool DocumentLoader::MaybeCreateArchive() { |
| // Give the archive machinery a crack at this document. If the MIME type is |
| // not an archive type, it will return 0. |
| if (!IsArchiveMIMEType(response_.MimeType())) |
| return false; |
| |
| DCHECK(main_resource_); |
| ArchiveResource* main_resource = |
| fetcher_->CreateArchive(main_resource_.Get()); |
| if (!main_resource) |
| return false; |
| // The origin is the MHTML file, we need to set the base URL to the document |
| // encoded in the MHTML so relative URLs are resolved properly. |
| EnsureWriter(main_resource->MimeType(), main_resource->Url()); |
| if (!frame_) |
| return false; |
| |
| // The MHTML page is loaded in full sandboxing mode with the only |
| // exception to open new top-level windows. Since the MHTML page stays in a |
| // unquie origin with script execution disabled, the risk to navigate to |
| // 'blob:'' and 'filesystem:'' URLs that allow code execution in the page's |
| // "real" origin is mitigated. |
| frame_->GetDocument()->EnforceSandboxFlags( |
| kSandboxAll & |
| ~(kSandboxPopups | kSandboxPropagatesToAuxiliaryBrowsingContexts)); |
| |
| CommitData(main_resource->Data()->Data(), main_resource->Data()->size()); |
| return true; |
| } |
| |
| const AtomicString& DocumentLoader::ResponseMIMEType() const { |
| return response_.MimeType(); |
| } |
| |
| const KURL& DocumentLoader::UnreachableURL() const { |
| return substitute_data_.FailingURL(); |
| } |
| |
| bool DocumentLoader::MaybeLoadEmpty() { |
| bool should_load_empty = !substitute_data_.IsValid() && |
| (request_.Url().IsEmpty() || |
| SchemeRegistry::ShouldLoadURLSchemeAsEmptyDocument( |
| request_.Url().Protocol())); |
| if (!should_load_empty) |
| return false; |
| |
| if (request_.Url().IsEmpty() && |
| !GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) |
| request_.SetURL(BlankURL()); |
| response_ = ResourceResponse(request_.Url(), "text/html", 0, g_null_atom); |
| FinishedLoading(MonotonicallyIncreasingTime()); |
| return true; |
| } |
| |
| void DocumentLoader::StartLoadingMainResource() { |
| GetTiming().MarkNavigationStart(); |
| DCHECK(!main_resource_); |
| DCHECK_EQ(state_, kNotStarted); |
| state_ = kProvisional; |
| |
| if (MaybeLoadEmpty()) |
| return; |
| |
| DCHECK(GetTiming().NavigationStart()); |
| |
| // PlzNavigate: |
| // The fetch has already started in the browser. Don't mark it again. |
| if (!frame_->GetSettings()->GetBrowserSideNavigationEnabled()) { |
| DCHECK(!GetTiming().FetchStart()); |
| GetTiming().MarkFetchStart(); |
| } |
| |
| DEFINE_STATIC_LOCAL( |
| ResourceLoaderOptions, main_resource_load_options, |
| (kDoNotBufferData, kAllowStoredCredentials, kClientRequestedCredentials, |
| kCheckContentSecurityPolicy, kDocumentContext)); |
| FetchParameters fetch_params(request_, FetchInitiatorTypeNames::document, |
| main_resource_load_options); |
| main_resource_ = |
| RawResource::FetchMainResource(fetch_params, Fetcher(), substitute_data_); |
| |
| // PlzNavigate: |
| // The final access checks are still performed here, potentially rejecting |
| // the "provisional" load, but the browser side already expects the renderer |
| // to be able to unconditionally commit. |
| if (!main_resource_ || |
| (frame_->GetSettings()->GetBrowserSideNavigationEnabled() && |
| main_resource_->ErrorOccurred())) { |
| request_ = ResourceRequest(BlankURL()); |
| MaybeLoadEmpty(); |
| return; |
| } |
| // A bunch of headers are set when the underlying resource load begins, and |
| // m_request needs to include those. Even when using a cached resource, we may |
| // make some modification to the request, e.g. adding the referer header. |
| request_ = main_resource_->IsLoading() ? main_resource_->GetResourceRequest() |
| : fetch_params.GetResourceRequest(); |
| main_resource_->AddClient(this); |
| } |
| |
| void DocumentLoader::EndWriting() { |
| writer_->end(); |
| writer_.Clear(); |
| } |
| |
| void DocumentLoader::DidInstallNewDocument(Document* document) { |
| document->SetReadyState(Document::kLoading); |
| document->InitContentSecurityPolicy(content_security_policy_.Release()); |
| |
| if (history_item_ && IsBackForwardLoadType(load_type_)) |
| document->SetStateForNewFormElements(history_item_->GetDocumentState()); |
| |
| String suborigin_header = response_.HttpHeaderField(HTTPNames::Suborigin); |
| if (!suborigin_header.IsNull()) { |
| Vector<String> messages; |
| Suborigin suborigin; |
| if (ParseSuboriginHeader(suborigin_header, &suborigin, messages)) |
| document->EnforceSuborigin(suborigin); |
| |
| for (auto& message : messages) { |
| document->AddConsoleMessage( |
| ConsoleMessage::Create(kSecurityMessageSource, kErrorMessageLevel, |
| "Error with Suborigin header: " + message)); |
| } |
| } |
| |
| document->GetClientHintsPreferences().UpdateFrom(client_hints_preferences_); |
| |
| // TODO(japhet): There's no reason to wait until commit to set these bits. |
| Settings* settings = document->GetSettings(); |
| fetcher_->SetImagesEnabled(settings->GetImagesEnabled()); |
| fetcher_->SetAutoLoadImages(settings->GetLoadsImagesAutomatically()); |
| |
| const AtomicString& dns_prefetch_control = |
| response_.HttpHeaderField(HTTPNames::X_DNS_Prefetch_Control); |
| if (!dns_prefetch_control.IsEmpty()) |
| document->ParseDNSPrefetchControlHeader(dns_prefetch_control); |
| |
| String header_content_language = |
| response_.HttpHeaderField(HTTPNames::Content_Language); |
| if (!header_content_language.IsEmpty()) { |
| size_t comma_index = header_content_language.find(','); |
| // kNotFound == -1 == don't truncate |
| header_content_language.Truncate(comma_index); |
| header_content_language = |
| header_content_language.StripWhiteSpace(IsHTMLSpace<UChar>); |
| if (!header_content_language.IsEmpty()) |
| document->SetContentLanguage(AtomicString(header_content_language)); |
| } |
| |
| OriginTrialContext::AddTokensFromHeader( |
| document, response_.HttpHeaderField(HTTPNames::Origin_Trial)); |
| String referrer_policy_header = |
| response_.HttpHeaderField(HTTPNames::Referrer_Policy); |
| if (!referrer_policy_header.IsNull()) { |
| UseCounter::Count(*document, UseCounter::kReferrerPolicyHeader); |
| document->ParseAndSetReferrerPolicy(referrer_policy_header); |
| } |
| |
| if (RuntimeEnabledFeatures::serverTimingEnabled() && |
| frame_->GetDocument()->domWindow()) { |
| DOMWindowPerformance::performance(*(frame_->GetDocument()->domWindow())) |
| ->AddServerTiming(response_, |
| PerformanceBase::ShouldAddToBuffer::Always); |
| } |
| |
| GetLocalFrameClient().DidCreateNewDocument(); |
| } |
| |
| void DocumentLoader::DidCommitNavigation() { |
| if (GetFrameLoader().StateMachine()->CreatingInitialEmptyDocument()) |
| return; |
| |
| if (!frame_->Loader().StateMachine()->CommittedMultipleRealLoads() && |
| load_type_ == kFrameLoadTypeStandard) { |
| frame_->Loader().StateMachine()->AdvanceTo( |
| FrameLoaderStateMachine::kCommittedMultipleRealLoads); |
| } |
| |
| GetLocalFrameClient().DispatchDidCommitLoad(history_item_.Get(), |
| LoadTypeToCommitType(load_type_)); |
| |
| // When the embedder gets notified (above) that the new navigation has |
| // committed, the embedder will drop the old Content Security Policy and |
| // therefore now is a good time to report to the embedder the Content |
| // Security Policies that have accumulated so far for the new navigation. |
| frame_->GetSecurityContext() |
| ->GetContentSecurityPolicy() |
| ->ReportAccumulatedHeaders(&GetLocalFrameClient()); |
| |
| // didObserveLoadingBehavior() must be called after dispatchDidCommitLoad() is |
| // called for the metrics tracking logic to handle it properly. |
| if (service_worker_network_provider_ && |
| service_worker_network_provider_->IsControlledByServiceWorker()) { |
| GetLocalFrameClient().DidObserveLoadingBehavior( |
| kWebLoadingBehaviorServiceWorkerControlled); |
| } |
| |
| // Links with media values need more information (like viewport information). |
| // This happens after the first chunk is parsed in HTMLDocumentParser. |
| DispatchLinkHeaderPreloads(nullptr, LinkLoader::kOnlyLoadNonMedia); |
| |
| TRACE_EVENT1("devtools.timeline", "CommitLoad", "data", |
| InspectorCommitLoadEvent::Data(frame_)); |
| probe::didCommitLoad(frame_, this); |
| frame_->GetPage()->DidCommitLoad(frame_); |
| } |
| |
| void SetFeaturePolicy(Document* document, const String& feature_policy_header) { |
| if (!RuntimeEnabledFeatures::featurePolicyEnabled()) |
| return; |
| LocalFrame* frame = document->GetFrame(); |
| WebFeaturePolicy* parent_feature_policy = |
| frame->IsMainFrame() |
| ? nullptr |
| : frame->Tree().Parent()->GetSecurityContext()->GetFeaturePolicy(); |
| Vector<String> messages; |
| const WebParsedFeaturePolicy& parsed_header = ParseFeaturePolicy( |
| feature_policy_header, frame->GetSecurityContext()->GetSecurityOrigin(), |
| &messages); |
| WebParsedFeaturePolicy container_policy; |
| if (frame->Owner()) |
| container_policy = frame->Owner()->ContainerPolicy(); |
| frame->GetSecurityContext()->InitializeFeaturePolicy( |
| parsed_header, container_policy, parent_feature_policy); |
| |
| for (auto& message : messages) { |
| document->AddConsoleMessage( |
| ConsoleMessage::Create(kOtherMessageSource, kErrorMessageLevel, |
| "Error with Feature-Policy header: " + message)); |
| } |
| if (!parsed_header.empty()) |
| frame->Client()->DidSetFeaturePolicyHeader(parsed_header); |
| } |
| |
| // static |
| bool DocumentLoader::ShouldClearWindowName( |
| const LocalFrame& frame, |
| SecurityOrigin* previous_security_origin, |
| const Document& new_document) { |
| if (!previous_security_origin) |
| return false; |
| if (!frame.IsMainFrame()) |
| return false; |
| if (frame.Loader().Opener()) |
| return false; |
| |
| return !new_document.GetSecurityOrigin()->IsSameSchemeHostPort( |
| previous_security_origin); |
| } |
| |
| void DocumentLoader::InstallNewDocument( |
| const DocumentInit& init, |
| const AtomicString& mime_type, |
| const AtomicString& encoding, |
| InstallNewDocumentReason reason, |
| ParserSynchronizationPolicy parsing_policy, |
| const KURL& overriding_url) { |
| DCHECK_EQ(init.GetFrame(), frame_); |
| DCHECK(!frame_->GetDocument() || !frame_->GetDocument()->IsActive()); |
| DCHECK_EQ(frame_->Tree().ChildCount(), 0u); |
| |
| SecurityOrigin* previous_security_origin = nullptr; |
| if (frame_->GetDocument()) |
| previous_security_origin = frame_->GetDocument()->GetSecurityOrigin(); |
| |
| if (!init.ShouldReuseDefaultView()) |
| frame_->SetDOMWindow(LocalDOMWindow::Create(*frame_)); |
| |
| Document* document = frame_->DomWindow()->InstallNewDocument(mime_type, init); |
| |
| if (ShouldClearWindowName(*frame_, previous_security_origin, *document)) { |
| // TODO(andypaicu): experimentalSetNullName will just record the fact |
| // that the name would be nulled and if the name is accessed after we will |
| // fire a UseCounter. If we decide to move forward with this change, we'd |
| // actually clean the name here. |
| // frame_->tree().setName(nullAtom); |
| frame_->Tree().ExperimentalSetNulledName(); |
| } |
| |
| frame_->GetPage()->GetChromeClient().InstallSupplements(*frame_); |
| if (!overriding_url.IsEmpty()) |
| document->SetBaseURLOverride(overriding_url); |
| DidInstallNewDocument(document); |
| |
| // This must be called before DocumentWriter is created, otherwise HTML parser |
| // will use stale values from HTMLParserOption. |
| if (reason == InstallNewDocumentReason::kNavigation) |
| DidCommitNavigation(); |
| |
| writer_ = |
| DocumentWriter::Create(document, parsing_policy, mime_type, encoding); |
| |
| // FeaturePolicy is reset in the browser process on commit, so this needs to |
| // be initialized and replicated to the browser process after commit messages |
| // are sent in didCommitNavigation(). |
| SetFeaturePolicy(document, |
| response_.HttpHeaderField(HTTPNames::Feature_Policy)); |
| |
| GetFrameLoader().DispatchDidClearDocumentOfWindowObject(); |
| } |
| |
| const AtomicString& DocumentLoader::MimeType() const { |
| if (writer_) |
| return writer_->MimeType(); |
| return response_.MimeType(); |
| } |
| |
| // This is only called by |
| // FrameLoader::replaceDocumentWhileExecutingJavaScriptURL() |
| void DocumentLoader::ReplaceDocumentWhileExecutingJavaScriptURL( |
| const DocumentInit& init, |
| const String& source) { |
| InstallNewDocument(init, MimeType(), |
| writer_ ? writer_->Encoding() : g_empty_atom, |
| InstallNewDocumentReason::kJavascriptURL, |
| kForceSynchronousParsing, KURL()); |
| if (!source.IsNull()) |
| writer_->AppendReplacingData(source); |
| EndWriting(); |
| } |
| |
| DEFINE_WEAK_IDENTIFIER_MAP(DocumentLoader); |
| |
| } // namespace blink |