成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從Chrome源碼看瀏覽器如何加載資源

系統 瀏覽器
本篇是研究了三個周末四五天的時間才得到的,而且為了避免錯誤不會隨便進行臆測,基本上每個小點都是實際debug執行和打印console得到的,經過驗證了才寫出來。

對瀏覽器加載資源有很多不確定性,例如:

  • css/font的資源的優化級會比img高,資源的優化級是怎么確定的呢?
  • 資源優先級又是如何影響加載的先后順序的?
  • 有幾種情況可能會導致資源被阻止加載?

通過源碼可以找到答案。此次源碼解讀基于Chromium 64(10月28日更新的源碼)。

下面通過加載資源的步驟,依次說明。

1. 開始加載

通過以下命令打開Chromium,同時打開一個網頁:

  1. chromium --renderer-startup-dialog https://www.baidu.com 

Chrome會在DocumentLoader.cpp里面通過以下代碼去加載:

  1. enum Type : uint8_t { 
  2.     kMainResource, 
  3.     kImage, 
  4.     kCSSStyleSheet, 
  5.     kScript, 
  6.     kFont, 
  7.     kRaw, 
  8.     kSVGDocument, 
  9.     kXSLStyleSheet, 
  10.     kLinkPrefetch, 
  11.     kTextTrack, 
  12.     kImportResource, 
  13.     kMedia,  // Audio or video file requested by a HTML5 media element 
  14.     kManifest, 
  15.     kMock  // Only for testing 
  16.   }; 

除了常見的image/css/js/font之外,我們發現還有像textTrack的資源,這個是什么東西呢?這個是video的字幕,使用webvtt格式:

  1. <video controls poster="/images/sample.gif"
  2.    <source src="sample.mp4" type="video/mp4"
  3.    <track kind="captions" src="sampleCaptions.vtt" srclang="en"
  4. </video> 

還有動態請求ajax屬于Raw類型。因為ajax可以請求多種資源。

MainResource包括location即導航輸入地址得到的頁面、使用frame/iframe嵌套的、通過超鏈接點擊的頁面以及表單提交這幾種。

接著交給稍底層的ResourceFecher去加載,所有資源都是通過它加載:

  1. fetcher->RequestResource( 
  2.       params, RawResourceFactory(Resource::kMainResource), substitute_data) 

在這個里面會先對請求做預處理。

2. 預處理請求

每發個請求會生成一個ResourceRequest對象,這個對象包含了http請求的所有信息:

包括url、http header、http body等,還有請求的優先級信息等:

然后會根據頁面的加載策略對這個請求做一些預處理,如下代碼:

  1. PrepareRequestResult result = PrepareRequest(params, factory, substitute_data, 
  2.                                               identifier, blocked_reason); 
  3.  if (result == kAbort) 
  4.    return nullptr; 
  5.  if (result == kBlock) 
  6.    return ResourceForBlockedRequest(params, factory, blocked_reason); 

prepareRequest會做兩件事情,一件是檢查請求是否合法,第二件是把請求做些修改。如果檢查合法性返回kAbort或者kBlock,說明資源被廢棄了或者被阻止了,就不去加載了。

被block的原因可能有以下幾種:

  1. enum class ResourceRequestBlockedReason { 
  2.   kCSP,              // CSP內容安全策略檢查 
  3.   kMixedContent,     // mixed content 
  4.   kOrigin,           // secure origin 
  5.   kInspector,        // devtools的檢查器 
  6.   kSubresourceFilter, 
  7.   kOther, 
  8.   kNone 
  9. }; 

源碼里面會在這個函數做合法性檢查:

  1. blocked_reason = Context().CanRequest(/*參數省略*/); 
  2.  if (blocked_reason != ResourceRequestBlockedReason::kNone) { 
  3.    return kBlock; 
  4.  } 

CanRequest函數會相應地檢查以下內容:

(1)CSP(Content Security Policy)內容安全策略檢查

CSP是減少XSS攻擊一個策略。如果我們只允許加載自己域的圖片的話,可以加上下面這個meta標簽:

  1. <meta http-equiv="Content-Security-Policy" content="img-src 'self';"

或者是后端設置這個http響應頭。

self表示本域,如果加載其它域的圖片瀏覽器將會報錯:

所以這個可以防止一些XSS注入的跨域請求。

源碼里面會檢查該請求是否符合CSP的設定要求:

  1. const ContentSecurityPolicy* csp = GetContentSecurityPolicy(); 
  2.   if (csp && !csp->AllowRequest( 
  3.                  request_context, url, options.content_security_policy_nonce, 
  4.                  options.integrity_metadata, options.parser_disposition, 
  5.                  redirect_status, reporting_policy, check_header_type)) { 
  6.     return ResourceRequestBlockedReason::kCSP; 
  7.   } 

如果有CSP并且AllowRequest沒有通過的話就會返回堵塞的原因。具體的檢查過程是根據不同的資源類型去獲取該類資源資源的CSP設定進行比較。

接著會根據CSP的要求改變請求:

  1. ModifyRequestForCSP(request); 

主要是升級http為https。

(2)upgrade-insecure-requests

如果設定了以下CSP規則:

  1. <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"

那么會將網頁的http請求強制升級為https,這是通過改變request對象實現的:

  1. url.SetProtocol("https"); 
  2.       if (url.Port() == 80) 
  3.         url.SetPort(443); 
  4.       resource_request.SetURL(url); 

包括改變url的協議和端口號。

(3)Mixed Content混合內容block

在https的網站請求http的內容就是Mixed Content,例如加載一個http的JS腳本,這種請求通常會被瀏覽器堵塞掉,因為http是沒有加密的,容易受到中間人的攻擊,如修改JS的內容,從而控制整個https的頁面,而圖片之類的資源即使內容被修改可能只是展示出問題,所以默認沒有block掉。源碼里面會檢查Mixed Content的內容:

  1. if (ShouldBlockFetchByMixedContentCheck(request_context, frame_type, 
  2.                                           resource_request.GetRedirectStatus(), 
  3.                                           url, reporting_policy)) 
  4.     return ResourceRequestBlockedReason::kMixedContent; 

在源碼里面,以下4種資源是optionally-blockable(被動混合內容):

  1. // "Optionally-blockable" mixed content 
  2.    case WebURLRequest::kRequestContextAudio: 
  3.    case WebURLRequest::kRequestContextFavicon: 
  4.    case WebURLRequest::kRequestContextImage: 
  5.    case WebURLRequest::kRequestContextVideo: 
  6.      return WebMixedContentContextType::kOptionallyBlockable; 

什么叫被動混合內容呢?W3C文檔是這么說的:那些不會打破頁面重要部分,風險比較低的,但是使用頻率又比較高的Mixed Content內容。

而剩下的其它所有資源幾乎都是blockable的,包括JS/CSS/Iframe/XMLHttpRequest等:

我們注意到img srcset里的資源也是默認會被阻止的,即下面的img會被block:

  1. <img srcset="http://fedren.com/test-1x.png 1x, http://fedren.com/test-2x.png 2x" alt> 

但是使用src的不會被block:

  1. <img src="http://fedren.com/images/sell/icon-home.png" alt> 

如下圖所示:

這就是optionally-blockable和blocakable資源的區分。

對于被動混合內容,如果設置strick mode:

  1. <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content"

那么即使是optionally的也會被block掉:

  1. case WebMixedContentContextType::kOptionallyBlockable: 
  2.       allowed = !strict_mode; 
  3.       if (allowed) { 
  4.         content_settings_client->PassiveInsecureContentFound(url); 
  5.         client->DidDisplayInsecureContent(); 
  6.       } 
  7.       break; 

上面代碼,如果strick_mode是true,allowed就是false,被動混合內容就會被阻止。

而對于主動混合內容,如果用戶設置允許加載:

 

那么也是可以加載的:

  1. case WebMixedContentContextType::kBlockable: { 
  2.      // Strictly block subresources that are mixed with respect to their 
  3.      // subframes, unless all insecure content is allowed. This is to avoid the 
  4.      // following situation: https://a.com embeds https://b.com, which loads a 
  5.      // script over insecure HTTP. The user opts to allow the insecure content, 
  6.      // thinking that they are allowing an insecure script to run on 
  7.      // https://a.com and not realizing that they are in fact allowing an 
  8.      // insecure script on https://b.com. 
  9.  
  10.      bool should_ask_embedder = 
  11.          !strict_mode && settings && 
  12.          (!settings->GetStrictlyBlockBlockableMixedContent() || 
  13.           settings->GetAllowRunningOfInsecureContent()); 
  14.      allowed = should_ask_embedder && 
  15.                content_settings_client->AllowRunningInsecureContent( 
  16.                    settings && settings->GetAllowRunningOfInsecureContent(), 
  17.                    security_origin, url); 
  18.      break; 

代碼倒數第4行會去判斷當前的client即當前頁面的設置是否允許加載blockable的資源。另外源碼注釋還提到了一種特殊的情況,就是a.com的頁面包含了b.com的頁面,b.com允許加載blockable的資源,a.com在非strick mode的時候頁面是允許加載的,但是如果a.com是strick mode,那么將不允許加載。

并且如果頁面設置了strick mode,用戶設置的允許blockable資源加載的設置將會失效:

  1. // If we're in strict mode, we'll automagically fail everything, and 
  2.  // intentionally skip the client checks in order to prevent degrading the 
  3.  // site's security UI. 
  4.  bool strict_mode = 
  5.      mixed_frame->GetSecurityContext()->GetInsecureRequestPolicy() & 
  6.          kBlockAllMixedContent || 
  7.      settings->GetStrictMixedContentChecking(); 

這個主要是svg使用use的獲取svg資源的時候必須不能跨域,如下以下資源將會被阻塞:

  1. <svg> 
  2.     <use href="http://cdn.test.com/images/logo.svg#abc"></use> 
  3. </svg> 

如下圖所示:

并且控制臺會打印:

源碼里面會對這種use link加載的svg做一個檢驗:

  1. case Resource::kSVGDocument: 
  2.       if (!security_origin->CanRequest(url)) { 
  3.         PrintAccessDeniedMessage(url); 
  4.         return ResourceRequestBlockedReason::kOrigin; 
  5.       } 
  6.       break; 

具體檢驗CanRequest函數主要是檢查是否同源:

  1. // We call isSameSchemeHostPort here instead of canAccess because we want 
  2.   // to ignore document.domain effects. 
  3.   if (IsSameSchemeHostPort(target_origin.get())) 
  4.     return true
  5.   
  6.   return false

如果協議、域名、端口號都一樣則通過檢查。需要這里和同源策略是兩碼事,這里的源阻塞是連請求都發不出去,而同源策略只是阻塞請求的返回結果。

svg的use外鏈一般是用來做svg的雪碧圖的,但是為什么需要同源呢,如果不同源會有什么不安全的因素?這里我也不清楚,暫時沒查到,W3C只是說明了需要同源,但沒有給出原因。

以上就是3種主要的block的原因。在預處理請求里面除了判斷資源有沒有被block或者abort(abort的原因通常是url不合法),還會計算資源的加載優先級。

3. 資源優先級

(1)計算資源加載優先級

通過調用以下函數設定:

  1. resource_request.SetPriority(ComputeLoadPriority( 
  2.      resource_type, params.GetResourceRequest(), ResourcePriority::kNotVisible, 
  3.      params.Defer(), params.GetSpeculativePreloadType(), 
  4.      params.IsLinkPreload())); 

我們來看一下這個函數里面是怎么計算當前資源的優先級的。

首先每個資源都有一個默認的優先級,這個優先級做為初始化值

  1. ResourceLoadPriority priority = TypeToPriority(type); 

不同類型的資源優先級是這么定義的:

  1. ResourceLoadPriority TypeToPriority(Resource::Type type) { 
  2.   switch (type) { 
  3.     case Resource::kMainResource: 
  4.     case Resource::kCSSStyleSheet: 
  5.     case Resource::kFont: 
  6.       // Also parser-blocking scripts (set explicitly in loadPriority) 
  7.       return kResourceLoadPriorityVeryHigh; 
  8.     case Resource::kXSLStyleSheet: 
  9.       DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); 
  10.     case Resource::kRaw: 
  11.     case Resource::kImportResource: 
  12.     case Resource::kScript: 
  13.       // Also visible resources/images (set explicitly in loadPriority) 
  14.       return kResourceLoadPriorityHigh; 
  15.     case Resource::kManifest: 
  16.     case Resource::kMock: 
  17.       // Also late-body scripts discovered by the preload scanner (set 
  18.       // explicitly in loadPriority) 
  19.       return kResourceLoadPriorityMedium; 
  20.     case Resource::kImage: 
  21.     case Resource::kTextTrack: 
  22.     case Resource::kMedia: 
  23.     case Resource::kSVGDocument: 
  24.       // Also async scripts (set explicitly in loadPriority) 
  25.       return kResourceLoadPriorityLow; 
  26.     case Resource::kLinkPrefetch: 
  27.       return kResourceLoadPriorityVeryLow; 
  28.   } 
  29.   
  30.   return kResourceLoadPriorityUnresolved; 

可以看到優先級總共分為五級:very-high、high、medium、low、very-low,其中MainRescource頁面、CSS、字體這三個的優先級是最高的,然后就是Script、Ajax這種,而圖片、音視頻的默認優先級是比較低的,最低的是prefetch預加載的資源。

什么是prefetch的資源呢?有時候你可能需要讓一些資源先加載好等著用,例如用戶輸入出錯的時候在輸入框右邊顯示一個X的圖片,如果等要顯示的時候再去加載就會有延時,這個時候可以用一個link標簽:

  1. <link rel="prefetch" href="image.png"

瀏覽器空閑的時候就會去加載。另外還可以預解析DNS:

  1. <link rel="dns-prefetch" href="https://cdn.test.com"

預建立TCP連接:  

  1. <link rel="preconnect" href="https://cdn.chime.me"

后面這兩個不屬于加載資源,這里順便提一下。

注意上面的switch-case設定資源優先級有一個順序,如果既是script又是prefetch的話得到的優化級是high,而不是prefetch的very low,因為prefetch是最后一個判斷。所以在設定了資源默認的優先級之后,會再對一些情況做一些調整,主要是對prefetch/preload的資源。包括:

a)降低preload的字體的優先級

如下代碼:

  1. // A preloaded font should not take precedence over critical CSS or 
  2.   // parser-blocking scripts. 
  3.   if (type == Resource::kFont && is_link_preload) 
  4.     priority = kResourceLoadPriorityHigh; 

會把預加載字體的優先級從very-high變成high

b)降低defer/async的script的優先級

如下代碼:

  1. if (type == Resource::kScript) { 
  2.     // Async/Defer: Low Priority (applies to both preload and parser-inserted) 
  3.     if (FetchParameters::kLazyLoad == defer_option) { 
  4.       priority = kResourceLoadPriorityLow; 
  5.     } 

script如果是defer的話,那么它優先級會變成最低。

4)頁面底部preload的script優先級變成medium

如下代碼:

  1. if (type == Resource::kScript) { 
  2.     // Special handling for scripts. 
  3.     // Default/Parser-Blocking/Preload early in document: High (set in 
  4.     // typeToPriority) 
  5.     // Async/Defer: Low Priority (applies to both preload and parser-inserted) 
  6.     // Preload late in document: Medium 
  7.     if (FetchParameters::kLazyLoad == defer_option) { 
  8.       priority = kResourceLoadPriorityLow; 
  9.     } else if (speculative_preload_type == 
  10.                    FetchParameters::SpeculativePreloadType::kInDocument && 
  11.                image_fetched_) { 
  12.       // Speculative preload is used as a signal for scripts at the bottom of 
  13.       // the document. 
  14.       priority = kResourceLoadPriorityMedium; 
  15.     } 

如果是defer的script那么優先級調成最低(上面第3小點),否則如果是preload的script,并且如果頁面已經加載了一張圖片就認為這個script是在頁面偏底部的位置,就把它的優先級調成medium。通過一個flag決定是否已經加載過第一張圖片了:

  1. // Resources before the first image are considered "early" in the document and 
  2.   // resources after the first image are "late" in the document.  Important to 
  3.   // note that this is based on when the preload scanner discovers a resource 
  4.   // for the most part so the main parser may not have reached the image element 
  5.   // yet. 
  6.   if (type == Resource::kImage && !is_link_preload) 
  7.     image_fetched_ = true

資源在第一張非preload的圖片前認為是early,而在后面認為是late,late的script的優先級會偏低。

什么叫preload呢?preload不同于prefetch的,在早期瀏覽器,script資源是阻塞加載的,當頁面遇到一個script,那么要等這個script下載和執行完了,才會繼續解析剩下的DOM結構,也就是說script是串行加載的,并且會堵塞頁面其它資源的加載,這樣會導致頁面整體的加載速度很慢,所以早在2008年的時候瀏覽器出了一個推測加載(speculative preload)策略,即遇到script的時候,DOM會停止構建,但是會繼續去搜索頁面需要加載的資源,如看下后續的html有沒有img/script標簽,先進行預加載,而不用等到構建DOM的時候才去加載。這樣大大提高了頁面整體的加載速度。

5)把同步即堵塞加載的資源的優先級調成最高

如下代碼:

  1. // A manually set priority acts as a floor. This is used to ensure that 
  2.  // synchronous requests are always given the highest possible priority 
  3.  return std::max(priority, resource_request.Priority()); 

如果是同步加載的資源,那么它的request對象里面的優先最級是最高的,所以本來是hight的ajax同步請求在最后return的時候會變成very-high。

這里是取了兩個值的最大值,第一個值是上面進行各種判斷得到的priority,第二個在初始這個ResourceRequest對象本身就有的一個優先級屬性,返回最大值后再重新設置resource_request的優先級屬性。

在構建resource request對象時所有資源都是最低的,這個可以從構造函數里面知道:

  1. ResourceRequest::ResourceRequest(const KURL& url) 
  2.     : url_(url), 
  3.       service_worker_mode_(WebURLRequest::ServiceWorkerMode::kAll), 
  4.       priority_(kResourceLoadPriorityLowest) 
  5.       /* 其它參數略 */ {} 

但是同步請求在初始化的時候會先設置成最高:

  1. void FetchParameters::MakeSynchronous() { 
  2.   // Synchronous requests should always be max priority, lest they hang the 
  3.   // renderer. 
  4.   resource_request_.SetPriority(kResourceLoadPriorityHighest); 
  5.   resource_request_.SetTimeoutInterval(10); 
  6.   options_.synchronous_policy = kRequestSynchronously; 

以上就是基本的資源加載優先級策略。

(2)轉換成Net的優先級

這個是在渲染線程里面進行的,上面提到的資源優先級在發請求之前會被轉化成Net的優先級:

  1. resource_request->priority = 
  2.       ConvertWebKitPriorityToNetPriority(request.GetPriority()); 

資源優先級對應Net的優先級關系如下所示:

畫成一個表:

Net Priority是請求資源的時候使用的,這個是在Chrome的IO線程里面進行的, 我在《JS與多線程》的Chrome的多線程模型里面提到,每個頁面都有Renderer線程負責渲染頁面,而瀏覽器有IO線程,用來負責請求資源等。為什么IO線程不是放在每個頁面里面而是放在瀏覽器框架呢?因為這樣的好處是如果兩個頁面請求了相同資源的話,如果有緩存的話就能避免重復請求了。

上面的都是在渲染線程里面debug操作得到的數據,為了能夠觀察資源請求的過程,需要切換到IO線程,而這兩個線程間的通信是通過Chrome封裝的Mojo框架進行的。在Renderer線程會發一個消息給IO線程通知它:

  1. mojo::Message message( 
  2.       internal::kURLLoaderFactory_CreateLoaderAndStart_Name, kFlags, 0, 0, nullptr); 
  3.  // 對這個message進行各種設置后(代碼略),調接收者的Accept函數  
  4.  ignore_result(receiver_->Accept(&message)); 

XCode里面可以看到這是在渲染線程RendererMain里操作的:

 

現在要切到Chrome的IO線程,把debug的方式改一下,如下選擇Chromium程序:

 

之前是使用Attach to Process把渲染進程的PID傳進來,因為每個頁面都是獨立的一個進程,現在要改成debug Chromium進程。然后在content/browser/loader/resource_scheduler.cc這個文件里的ShouldStartRequest函數里面打個斷點,接著在Chromium里面打開一個網頁,就可以看到斷點生效了。在XCode里面可以看到當前線程名稱叫Chrome_IOThread:

這與上面的描述一致。IO線程是如何利用優先級決定要不要開始加載資源的呢?

(3)資源加載

上面提到的ShouldStartRequest這個函數是判斷當前資源是否能開始加載了,如果能的話就準備加載了,如果不能的話就繼續把它放到pending request隊列里面,如下代碼所示:

  1. void ScheduleRequest(const net::URLRequest& url_request, 
  2.                       ScheduledResourceRequest* request) { 
  3.    SetRequestAttributes(request, DetermineRequestAttributes(request)); 
  4.    ShouldStartReqResult should_start = ShouldStartRequest(request); 
  5.    if (should_start == START_REQUEST) { 
  6.      // New requests can be started synchronously without issue. 
  7.      StartRequest(request, START_SYNC, RequestStartTrigger::NONE); 
  8.    } else { 
  9.      pending_requests_.Insert(request); 
  10.    } 
  11.  } 

一旦收到Mojo的加載資源消息就會調上面的ScheduleRequest函數,除了收到消息之外,還有一個地方也會調用:

  1. void LoadAnyStartablePendingRequests(RequestStartTrigger trigger) { 
  2.    // We iterate through all the pending requests, starting with the highest 
  3.    // priority one.  
  4.    RequestQueue::NetQueue::iterator request_iter = 
  5.        pending_requests_.GetNextHighestIterator(); 
  6.  
  7.    while (request_iter != pending_requests_.End()) { 
  8.      ScheduledResourceRequest* request = *request_iter; 
  9.      ShouldStartReqResult query_result = ShouldStartRequest(request); 
  10.  
  11.      if (query_result == START_REQUEST) { 
  12.        pending_requests_.Erase(request); 
  13.        StartRequest(request, START_ASYNC, trigger); 
  14.      } 
  15.  } 

這個函數的特點是遍歷pending requests,每次取出優先級最高的一個request,然后調ShouldStartRequest判斷是否能運行了,如果能的話就把它從pending requests里面刪掉,然后運行。

而這個函數會有三個地方會調用,一個是IO線程的循環判斷,只要還有未完成的任務,就會觸發加載,第二個是當有請求完成時會調,第三個是要插入body標簽的時候。所以主要總共有三個地方會觸發加載:

(1)收到來自渲染線程IPC::Mojo的請求加載資源的消息

(2)每個請求完成之后,觸發加載pending requests里還未加載的請求

(3)IO線程定時循環未完成的任務,觸發加載

  1. <!DOCType html> 
  2. <html> 
  3. <head> 
  4.     <meta charset="utf-8"
  5.     <link rel="icon" href="4.png"
  6.     <img src="0.png"
  7.     <img src="1.png"
  8.     <link rel="stylesheet" href="1.css"
  9.     <link rel="stylesheet" href="2.css"
  10.     <link rel="stylesheet" href="3.css"
  11.     <link rel="stylesheet" href="4.css"
  12.     <link rel="stylesheet" href="5.css"
  13.     <link rel="stylesheet" href="6.css"
  14.     <link rel="stylesheet" href="7.css"
  15. </head> 
  16. <body> 
  17.     <p>hello</p> 
  18.     <img src="2.png"
  19.     <img src="3.png"
  20.     <img src="4.png"
  21.     <img src="5.png"
  22.     <img src="6.png"
  23.     <img src="7.png"
  24.     <img src="8.png"
  25.     <img src="9.png"
  26.   
  27.     <script src="1.js"></script> 
  28.     <script src="2.js"></script> 
  29.     <script src="3.js"></script> 
  30.   
  31.     <img src="3.png"
  32. <script> 
  33. !function(){ 
  34.     let xhr = new XMLHttpRequest(); 
  35.     xhr.open("GET""https://baidu.com"); 
  36.     xhr.send(); 
  37.     document.write("hi"); 
  38. }(); 
  39. </script> 
  40. <link rel="stylesheet" href="9.css"
  41. </body> 
  42. </html> 

知道了觸發加載機制之的,接著研究具體優先加載的過程,用以下html做為demo:

然后把Chrome的網絡速度調為Fast 3G,讓加載速度降低,以便更好地觀察這個過程,結果如下圖所示:

從上圖可以發現以下特點:

(1)每個域每次最多同時加載6個資源(http/1.1)

(2)CSS具有最高的優先級,最先加載,即使是放在最后面9.css也是比前面資源先開始加載

(3)JS比圖片優先加載,即使出現得比圖片晚

(4)只有等CSS都加載完了,才能加載其它的資源,即使這個時候沒有達到6個的限制

(5)head里面的非高優化級的資源最多能先加載一張(0.png)

(6)xhr的資源雖然具有高優先級,但是由于它是排在3.js后面的,JS的執行是同步的,所以它排得比較靠后,如果把它排在1.js前面,那么它也會比圖片先加載。

為什么是這樣呢?我們從源碼尋找答案。

首先認清幾個概念,請求可分為delayable和none-delayable兩種:

  1. // The priority level below which resources are considered to be delayable. 
  2. static const net::RequestPriority 
  3.     kDelayablePriorityThreshold = net::MEDIUM; 

在優先級在Medium以下的為delayable,即可推遲的,而大于等于Medium的為不可delayable的。從剛剛我們總結的表可以看出:css/js是不可推遲的,而圖片、preload的js為可推遲加載:

還有一種是layout-blocking的請求:

  1. // The priority level above which resources are considered layout-blocking if 
  2. // the html_body has not started. 
  3. static const net::RequestPriority 
  4.     kLayoutBlockingPriorityThreshold = net::MEDIUM; 

這是當還沒有渲染body標簽,并且優先級在Medium之上的如CSS的請求。

然后,上面提到的ShouldStartRequest函數,這個函數是規劃資源加載順序最主要的函數,從源碼注釋可以知道它大概的過程:

  1. // ShouldStartRequest is the main scheduling algorithm. 
  2.   // 
  3.   // Requests are evaluated on five attributes: 
  4.   // 
  5.   // 1. Non-delayable requests: 
  6.   //   * Synchronous requests. 
  7.   //   * Non-HTTP[S] requests. 
  8.   // 
  9.   // 2. Requests to request-priority-capable origin servers. 
  10.   // 
  11.   // 3. High-priority requests: 
  12.   //   * Higher priority requests (> net::LOW). 
  13.   // 
  14.   // 4. Layout-blocking requests: 
  15.   //   * High-priority requests (> net::MEDIUM) initiated before the renderer has 
  16.   //     a <body>. 
  17.   // 
  18.   // 5. Low priority requests 
  19.   // 
  20.   //  The following rules are followed: 
  21.   // 
  22.   //  All types of requests: 
  23.   //   * Non-delayable, High-priority and request-priority capable requests are 
  24.   //     issued immediately. 
  25.   //   * Low priority requests are delayable. 
  26.   //   * While kInFlightNonDelayableRequestCountPerClientThreshold(=1) 
  27.   //     layout-blocking requests are loading or the body tag has not yet been 
  28.   //     parsed, limit the number of delayable requests that may be in flight 
  29.   //     to kMaxNumDelayableWhileLayoutBlockingPerClient(=1). 
  30.   //   * If no high priority or layout-blocking requests are in flight, start 
  31.   //     loading delayable requests. 
  32.   //   * Never exceed 10 delayable requests in flight per client. 
  33.   //   * Never exceed 6 delayable requests for a given host. 

從上面的注釋可以得到以下信息:

(1)高優先級的資源(>=Medium)、同步請求和非http(s)的請求能夠立刻加載

(2)只要有一個layout blocking的資源在加載,最多只能加載一個delayable的資源,這個就解釋了為什么0.png能夠先加載

(3)只有當layout blocking和high priority的資源加載完了,才能開始加載delayable的資源,這個就解釋了為什么要等CSS加載完了才能加載其它的js/圖片。

(4)同時加載的delayable資源同一個域只能有6個,同一個client即同一個頁面最多只能有10個,否則要進行排隊。

注意這里說的開始加載,并不是說能夠開始請求建立連接了。源碼里面叫in flight,在飛行中,而不是叫in request之類的,能夠進行in flight的請求是指那些不用queue的請求,如下圖:

 

白色條是指queue的時間段,而灰色的是已經in flight了但受到同域只能最多只能建立6個TCP連接等的影響而進入的stalled狀態,綠色是TTFB(Time to First Byte)從開始建立TCP連接到收到第一個字節的時間,藍色是下載的時間。

我們已經解釋了大部分加載的特點的原因,對著上面那張圖可以再重述一次:

(1)由于1.css到9.css這幾個CSS文件是high priority或者是none delayable的,所以馬上in flight,但是還受到了同一個域最多只能有6個的限制,所以6/7/9.css這三個進入stalled的狀態

(2)1.css到5.css是layout-blocking的,所以最多只能再加載一個delayable的0.png,在它相鄰的1.png就得排隊了

(3)等到high priority和layout-blocking的資源7.css/9.css/1.js加載完了,就開始加載delayable的資源,主要是preload的js和圖片

這里有個問題,為什么1.js是high priority的而2.js和3.js卻是delayable的?為此在源碼的ShouldStartRequest函數里面添加一些代碼,把每次判斷請求的一些關鍵信息打印出來:

  1. LOG(INFO) << "url: " << url_request.url().spec() << " priority: " << url_request.priority() 
  2.     << " has_html_body_: " << has_html_body_ << " delayable: " 
  3.     << RequestAttributesAreSet(request->attributes(), kAttributeDelayable); 

把打印出來的信息按順序畫成以下表格:

1.js的優先級一開始是Low的,即是delayable的,但是后面又變成了Medium就不是delayable了,是high priority,為什么它的優先級能夠提高呢?一開始是Low是因為它是推測加載的,所以是優先級比較低,但是當DOM構建到那里的時候它就不是preload的,變成正常的JS加載了,所以它的優先級變成了Medium,這個可以從has_html_body標簽進行推測,而2.js要等到1.js下載和解析完,它能算是正常加載,否則還是推測加載,因此它的優先級沒有得到提高。

本次解讀到這里告一段落,我們得到了有3種原因會阻止加載資源,包括CSP、Mixed Content、Origin block,CSP是自己手動設置的一些限制,Mixed Content是https頁面不允許加載http的內容,Origin Block主要是svg的href只能是同源的資源。還知道了瀏覽器把資源歸成CSS/Font/JS/Image等幾類,總共有5種優先級,從Lowest到Highest,每種資源都會設定一個優先級,總的來說CSS/Font/Frame和同步請求這四種的優先級是最高的,不能推遲加載的,而正常加載的JS屬于高優先級,推測加載preload則優先級會比較低,會推遲加載。并且如果有layout blocking的請求的話,那么delayable的資源要等到高優先級的加載完了才能進行加載。已經開始加載的資源還可能會處于stalled的狀態,因為每個域同時建立的TCP連接數是有限的。

但是我們還有很多問題沒有得到解決,例如:

(1)同源策略具體是怎樣處理的?

(2)優先級是如何動態改變的?

(3)http cache/service worker是如何影響資源加載的?

我們將嘗試在下一次解讀進行回答,看源碼是一件比較費時費力的事情,本篇是研究了三個周末四五天的時間才得到的,而且為了避免錯誤不會隨便進行臆測,基本上每個小點都是實際debug執行和打印console得到的,經過驗證了才寫出來。但是由于看的深度有限和理解偏差,可能會有一些不全面的地方甚至錯誤,但是從注釋可以看到有些地方為什么有這個判斷條件即使是源碼的維護者也不太確定。本篇解讀盡可能地實事求事。

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2017-02-28 10:05:56

Chrome源碼

2017-02-07 09:44:12

Chrome源碼DOM樹

2017-02-09 15:15:54

Chrome瀏覽器

2011-06-21 16:52:48

2012-07-04 17:00:06

獵豹瀏覽瀏覽器

2010-01-28 10:13:43

2009-11-26 10:55:41

2020-05-15 15:23:25

Chrome瀏覽器谷歌

2015-01-21 15:45:50

斯巴達瀏覽器

2018-02-02 15:48:47

ChromeDNS解析

2021-01-07 07:52:04

瀏覽器網頁資源加載

2010-01-10 17:50:17

2009-07-17 09:16:20

Google Chro瀏覽器操作系統

2019-02-15 15:15:59

ChromeJavascriptHtml

2009-09-22 09:17:46

谷歌Chrome瀏覽器

2012-08-08 09:18:47

Chrome瀏覽器

2013-11-13 15:54:20

Chrome 31瀏覽器

2009-12-06 09:38:02

Chrome瀏覽器Avast

2009-03-07 09:57:41

Realplayer捆綁Chrome

2009-12-03 10:56:34

谷歌Chrome瀏覽器
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品久久在线观看 | 午夜电影福利 | 日韩高清中文字幕 | 国产在线视频一区 | 欧美激情一区 | 亚洲精品欧美精品 | 91污在线 | 久久av网 | 不卡的av在线 | 二区中文字幕 | 伊人伊人网 | 久久成人综合 | 久久精品国产一区 | 成人精品久久久 | 欧美日韩在线一区二区 | 伊人免费在线观看 | 久久一区二区三区四区 | 男人的天堂久久 | 一级黄色毛片免费 | 国产精品毛片一区二区在线看 | 三级在线免费 | 日韩欧美精品 | 免费欧美| 久久99这里只有精品 | 91欧美激情一区二区三区成人 | 国产精品久久久久久久久久免费看 | 亚洲一区国产精品 | 国产精品久久网 | 国产精品不卡 | 99热这里有精品 | 国产999精品久久久 午夜天堂精品久久久久 | 91久久精品一区二区二区 | 天天综合国产 | 久久免费精品 | 国产成人精品亚洲日本在线观看 | 精品国产乱码久久久久久久久 | 91精品久久久久久久99 | 国产成人精品在线播放 | 亚洲精品视 | 日韩欧美中文字幕在线观看 | 欧美日韩成人在线 |