精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

從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瀏覽器谷歌

2021-01-07 07:52:04

瀏覽器網頁資源加載

2015-01-21 15:45:50

斯巴達瀏覽器

2018-02-02 15:48:47

ChromeDNS解析

2010-01-10 17:50:17

2009-09-22 09:17:46

谷歌Chrome瀏覽器

2012-08-08 09:18:47

Chrome瀏覽器

2019-02-15 15:15:59

ChromeJavascriptHtml

2009-07-17 09:16:20

Google Chro瀏覽器操作系統

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精品国产高清一区二区三区蜜臀| 久久精品美女| 日韩乱码一区二区三区| 91日韩欧美| 日韩免费电影网站| 欧美牲交a欧美牲交aⅴ免费下载| av在线电影网| 高清成人免费视频| 欧美在线视频网| 午夜爽爽爽男女免费观看| 99这里只有精品视频| 91黄色免费看| 久久亚洲a v| 国产精品免费播放| 国产69精品久久久久毛片| 欧美在线一区二区三区四| 91久久国产综合| 最新亚洲精品| 欧美大胆人体bbbb| 啊啊啊国产视频| 成人女同在线观看| 亚洲天堂久久久久久久| 鲁鲁狠狠狠7777一区二区| 国产精品人人爽| 久久只有精品| 韩国v欧美v日本v亚洲| 国产三级在线观看完整版| 精品三级在线观看视频| 欧美一区二区视频在线观看2020| 激情五月开心婷婷| 福利写真视频网站在线| 一区在线播放视频| 欧美精品一区二区三区四区五区| 性欧美videos另类hd| 美国毛片一区二区| 国产www精品| 中国一级特黄毛片| 91久久午夜| 欧美高清在线观看| 亚洲av无码一区二区三区在线| 欧美人妖在线| 国产婷婷色综合av蜜臀av| 免费看三级黄色片| 91精品网站在线观看| 欧美性三三影院| mm1313亚洲国产精品无码试看| 欧产日产国产精品视频| 亚洲成a人在线观看| 国产91porn| 怡红院在线播放| 亚洲欧美日韩一区二区| 伊人狠狠色丁香综合尤物| 户外极限露出调教在线视频| 久久亚洲综合av| 久久精品国产精品国产精品污| 不卡视频免费在线观看| 国产在线乱码一区二区三区| 成人免费看吃奶视频网站| а中文在线天堂| 蜜桃视频一区二区| 国产在线不卡精品| 一区二区三区免费观看视频| 久久机这里只有精品| 国产男人精品视频| 国产又粗又猛又爽又黄视频 | 成人午夜大片免费观看| 97神马电影| 日韩专区第一页| 91日韩精品一区| 蜜桃999成人看片在线观看| 神马久久久久| 国产欧美日韩精品在线| 亚洲精品电影在线一区| 91黄色在线| 婷婷六月综合亚洲| 久久无码高潮喷水| 欧美影视资讯| 4hu四虎永久在线影院成人| 天天爽夜夜爽视频| 欧美91在线| 曰本色欧美视频在线| 久艹在线观看视频| 国产专区一区| 日韩免费观看视频| 一区二区三区日| 高清免费成人av| 免费中文日韩| 国产日产一区二区三区| 亚洲午夜久久久久中文字幕久| 日韩激情免费视频| 国产福利亚洲| 亚洲丁香婷深爱综合| 一级片视频免费看| 欧美激情日韩| 国产成人精品久久久| 亚洲天堂网视频| 成人精品国产免费网站| 日韩国产高清一区| 91精品久久| 91成人在线精品| 超碰人人cao| 黑丝美女一区二区| 欧美成人免费在线视频| 国产美女激情视频| 国产麻豆9l精品三级站| 欧美一区1区三区3区公司| www.久久久久.com| 色综合天天视频在线观看| 黄色一级片免费播放| 欧美五码在线| 久久在线免费视频| 免费黄色片视频| 成人免费高清在线| 亚洲一卡二卡区| 国产中文在线播放| 日韩一区二区三区视频在线 | 日韩免费视频播放| 国产麻豆一区| 精品一区精品二区| 免费无码毛片一区二区app| 三级欧美在线一区| 国产一区二区免费在线观看| 顶级网黄在线播放| 欧美亚洲国产一区二区三区va| 日本美女视频网站| 欧美电影一二区| 日韩av电影手机在线观看| 国产 欧美 自拍| 自拍偷拍国产精品| 日本久久久久久久久久久久| 任你躁在线精品免费| 久久999免费视频| 一级做a爱片性色毛片| 久久久久一区二区三区四区| 全黄性性激高免费视频| 清纯唯美激情亚洲| 久久激情五月丁香伊人| 亚洲熟女乱色一区二区三区久久久| 91丨九色丨蝌蚪丨老版| 欧美极品欧美精品欧美| 国产伦理久久久久久妇女 | 欧美精品日韩精品| 人妻aⅴ无码一区二区三区| 国产欧美午夜| 久久超碰亚洲| 成人在线黄色电影| 亚洲成人动漫在线播放| www.youjizz.com亚洲| 国产**成人网毛片九色| 日韩黄色片在线| 亚洲图色一区二区三区| 欧美精品日韩www.p站| 精品乱子伦一区二区| 亚洲三级免费电影| 久久久久亚洲av片无码v| 欧美99久久| 99精品国产一区二区| 亚洲色图美国十次| 欧美sm美女调教| 日本一级黄色大片| 91一区在线观看| 粗暴91大变态调教| 成人免费在线观看av| 国产日韩欧美在线看| 黄色在线观看网站| 欧美成人a视频| 日本视频免费在线| 国产午夜精品久久久久久久 | 成人看片网页| 中文字幕视频一区二区在线有码| 91久久国语露脸精品国产高跟| 亚洲欧洲性图库| 精品人妻二区中文字幕| 国产欧美日本| 夜夜爽www精品| 在线精品视频一区| 欧美专区在线观看| 成人av毛片| 日韩免费视频一区| 青青草免费观看视频| 中文字幕第一区第二区| wwwxxxx在线观看| 久久国产日韩| 成人手机视频在线| 精品资源在线| 91精品国产综合久久香蕉最新版 | 日本中文字幕一区二区有码在线 | 日韩精品社区| 国产日韩精品在线| 波多野结依一区| 国产亚洲精品久久久优势| 一级黄色片在线播放| 性做久久久久久| 大地资源高清在线视频观看| 成人av电影在线| 国产wwwxx| 1024成人| 中文字幕一区综合| 欧美日韩精品一区二区三区在线观看| 国产精品亚洲一区二区三区| 黄网在线免费看| 中文字幕无线精品亚洲乱码一区 | 亚洲日本中文字幕| 精品人妻一区二区三区浪潮在线 | 在线免费观看视频网站| 亚洲成人av一区| 国产传媒免费在线观看| 91美女片黄在线观看| 午夜福利123| 日韩电影在线观看一区| 欧美久久久久久久久久久久久| 久久国产中文字幕| 久久久国产精品一区二区三区| 国内精品视频| 国产精品久久久久久av福利软件 | 激情小视频在线| 精品国产乱码久久久久久老虎| 中文字字幕在线中文乱码| 精品久久久久人成| 麻豆视频在线观看| 亚洲欧美综合另类在线卡通| 亚洲无人区码一码二码三码的含义| 成人av免费网站| 日韩av成人网| 国产乱子伦一区二区三区国色天香| 国产av人人夜夜澡人人爽| 99精品国产99久久久久久福利| 9色视频在线观看| 99久久精品国产亚洲精品| 日韩在线导航| 亚洲男人都懂第一日本| 精品婷婷色一区二区三区蜜桃| 成人免费在线电影网| 国产精品初高中精品久久| 精品三级国产| 亚洲精品免费网站| 伊人久久一区| 国产视频福利一区| 九九热这里有精品| 国产精品一区久久久| 日韩欧美一区二区三区免费观看| 欧美中文字幕视频在线观看| 亚洲黄色免费看| 97成人精品区在线播放| 黄频免费在线观看| 欧美亚洲伦理www| 是的av在线| 日韩美女主播视频| 78精品国产综合久久香蕉| 国产脚交av在线一区二区| 欧洲亚洲两性| 国产精品视频内| 欧洲精品久久久久毛片完整版| 国产精品入口夜色视频大尺度| 久久天堂av| 国产欧美日韩视频| 综合欧美精品| av日韩免费电影| 国产乱论精品| 日本午夜精品电影| 天天揉久久久久亚洲精品| 9色视频在线观看| 亚洲黄色高清| 国产精品视频一区二区三区四区五区| 久久国产日本精品| jizz大全欧美jizzcom| 国产做a爰片久久毛片| 香蕉久久久久久av成人| 成人国产免费视频| 欧美做受高潮6| 中文字幕一区二区三区在线不卡| 国产suv一区二区三区| 同产精品九九九| 99久久久无码国产精品免费蜜柚| 欧美人与禽zozo性伦| 性生活视频软件| 日韩精品电影网| 91美女视频在线| 欧美国产日本高清在线| 中文字幕乱码在线播放| 国产日本欧美一区二区三区在线 | 欧美女同一区| 日韩av第一页| 色妞ww精品视频7777| 麻豆av一区| 久久久久久免费视频| 蜜桃传媒一区二区三区| 裸体一区二区三区| 99久久久无码国产精品性波多 | 欧美日韩在线高清| 亚洲国产精品91| 成人免费在线小视频| 经典三级在线一区| 欧洲美一区二区三区亚洲| 综合电影一区二区三区| 日韩人妻无码一区二区三区99| 欧美午夜影院一区| 日韩中文字幕观看| 日韩有码在线电影| 中文字幕在线看片| 波多野结衣精品久久| 狠狠操综合网| 欧美成人高潮一二区在线看| 精品伊人久久久久7777人| 国产免费看av| 亚洲一区在线电影| 国产精品久久欧美久久一区| 日韩精品日韩在线观看| 91中文在线| 国产精品一区二区性色av| 夜色77av精品影院| 国产自产在线视频| 激情综合色综合久久综合| 精品成人无码一区二区三区| 亚洲成av人片| 99视频免费看| 久久精品精品电影网| 欧美xnxx| 欧美高清性xxxxhd| 亚洲激情社区| 四虎国产精品免费| 亚洲视频在线观看一区| 天天射天天干天天| 亚洲精品国产品国语在线| 美女精品导航| 91在线精品播放| 首页国产精品| 我要看一级黄色大片| 久久―日本道色综合久久| 欧美亚洲天堂网| 日韩女优制服丝袜电影| 超碰个人在线| 91精品久久久久久久久久 | 伊人伊成久久人综合网小说| 三妻四妾完整版在线观看电视剧| 官网99热精品| 伊人久久婷婷| 中文字幕人妻一区| 亚洲一级二级三级| 亚洲精品一区二区三区新线路 | 在线男人天堂| 久久久亚洲综合网站| 一本久道综合久久精品| 黄色av网址在线观看| 亚洲a一区二区| 色综合久久久久久| 欧美一区视频在线| 伊人春色之综合网| www.色偷偷.com| 欧美国产日韩精品免费观看| 在线视频精品免费| 色综合亚洲精品激情狠狠| 欧美性生活一级| 国产a级片免费看| 国产成人在线色| 国产污视频在线看| 亚洲男人天堂2023| 日韩av免费| 美女在线免费视频| 成人午夜短视频| 国产一级淫片a视频免费观看| 国产亚洲欧美aaaa| 日本电影久久久| 污污污污污污www网站免费| 成人小视频在线观看| 中文字幕第四页| 日韩中文字幕视频| 免费精品一区| 日韩激情免费视频| 国产精品网站导航| 国产后入清纯学生妹| 午夜精品一区二区三区在线| 国产一区二区电影在线观看| 一级黄色特级片| 亚洲一区二区美女| 久久精品蜜桃| 91久久在线播放| 日韩一级大片| 少妇太紧太爽又黄又硬又爽小说| 欧美一区二区三区不卡| 91福利在线免费| 色涩成人影视在线播放| 国产在线精品不卡| 日韩精品乱码久久久久久| 中文字幕日韩有码| 加勒比色老久久爱综合网| 一区二区三区免费播放| 一区二区三区在线观看动漫 | 日韩在线亚洲| 欧美极品欧美精品欧美| 国产精品不卡视频| 你懂的网站在线| 国产玖玖精品视频| 91久久午夜| 日韩va亚洲va欧美va清高| 亚洲黄色片网站| 国产精品igao视频网网址不卡日韩 | 欧美大片久久久| 黑人精品xxx一区一二区|