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

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

系統(tǒng) 瀏覽器
本篇是研究了三個(gè)周末四五天的時(shí)間才得到的,而且為了避免錯(cuò)誤不會(huì)隨便進(jìn)行臆測(cè),基本上每個(gè)小點(diǎn)都是實(shí)際debug執(zhí)行和打印console得到的,經(jīng)過驗(yàn)證了才寫出來。

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

  • css/font的資源的優(yōu)化級(jí)會(huì)比img高,資源的優(yōu)化級(jí)是怎么確定的呢?
  • 資源優(yōu)先級(jí)又是如何影響加載的先后順序的?
  • 有幾種情況可能會(huì)導(dǎo)致資源被阻止加載?

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

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

1. 開始加載

通過以下命令打開Chromium,同時(shí)打開一個(gè)網(wǎng)頁:

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

Chrome會(huì)在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之外,我們發(fā)現(xiàn)還有像textTrack的資源,這個(gè)是什么東西呢?這個(gè)是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> 

還有動(dòng)態(tài)請(qǐng)求ajax屬于Raw類型。因?yàn)閍jax可以請(qǐng)求多種資源。

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

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

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

在這個(gè)里面會(huì)先對(duì)請(qǐng)求做預(yù)處理。

2. 預(yù)處理請(qǐng)求

每發(fā)個(gè)請(qǐng)求會(huì)生成一個(gè)ResourceRequest對(duì)象,這個(gè)對(duì)象包含了http請(qǐng)求的所有信息:

包括url、http header、http body等,還有請(qǐng)求的優(yōu)先級(jí)信息等:

然后會(huì)根據(jù)頁面的加載策略對(duì)這個(gè)請(qǐng)求做一些預(yù)處理,如下代碼:

  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會(huì)做兩件事情,一件是檢查請(qǐng)求是否合法,第二件是把請(qǐng)求做些修改。如果檢查合法性返回kAbort或者kBlock,說明資源被廢棄了或者被阻止了,就不去加載了。

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

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

源碼里面會(huì)在這個(gè)函數(shù)做合法性檢查:

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

CanRequest函數(shù)會(huì)相應(yīng)地檢查以下內(nèi)容:

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

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

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

或者是后端設(shè)置這個(gè)http響應(yīng)頭。

self表示本域,如果加載其它域的圖片瀏覽器將會(huì)報(bào)錯(cuò):

所以這個(gè)可以防止一些XSS注入的跨域請(qǐng)求。

源碼里面會(huì)檢查該請(qǐng)求是否符合CSP的設(shè)定要求:

  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沒有通過的話就會(huì)返回堵塞的原因。具體的檢查過程是根據(jù)不同的資源類型去獲取該類資源資源的CSP設(shè)定進(jìn)行比較。

接著會(huì)根據(jù)CSP的要求改變請(qǐng)求:

  1. ModifyRequestForCSP(request); 

主要是升級(jí)http為https。

(2)upgrade-insecure-requests

如果設(shè)定了以下CSP規(guī)則:

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

那么會(huì)將網(wǎng)頁的http請(qǐng)求強(qiáng)制升級(jí)為https,這是通過改變r(jià)equest對(duì)象實(shí)現(xiàn)的:

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

包括改變url的協(xié)議和端口號(hào)。

(3)Mixed Content混合內(nèi)容block

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

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

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

  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; 

什么叫被動(dòng)混合內(nèi)容呢?W3C文檔是這么說的:那些不會(huì)打破頁面重要部分,風(fēng)險(xiǎn)比較低的,但是使用頻率又比較高的Mixed Content內(nèi)容。

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

我們注意到img srcset里的資源也是默認(rèn)會(huì)被阻止的,即下面的img會(huì)被block:

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

但是使用src的不會(huì)被block:

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

如下圖所示:

這就是optionally-blockable和blocakable資源的區(qū)分。

對(duì)于被動(dòng)混合內(nèi)容,如果設(shè)置strick mode:

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

那么即使是optionally的也會(huì)被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,被動(dòng)混合內(nèi)容就會(huì)被阻止。

而對(duì)于主動(dòng)混合內(nèi)容,如果用戶設(shè)置允許加載:

 

那么也是可以加載的:

  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; 

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

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

  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(); 

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

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

如下圖所示:

并且控制臺(tái)會(huì)打印:

源碼里面會(huì)對(duì)這種use link加載的svg做一個(gè)檢驗(yàn):

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

具體檢驗(yàn)CanRequest函數(shù)主要是檢查是否同源:

  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

如果協(xié)議、域名、端口號(hào)都一樣則通過檢查。需要這里和同源策略是兩碼事,這里的源阻塞是連請(qǐng)求都發(fā)不出去,而同源策略只是阻塞請(qǐng)求的返回結(jié)果。

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

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

3. 資源優(yōu)先級(jí)

(1)計(jì)算資源加載優(yōu)先級(jí)

通過調(diào)用以下函數(shù)設(shè)定:

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

我們來看一下這個(gè)函數(shù)里面是怎么計(jì)算當(dāng)前資源的優(yōu)先級(jí)的。

首先每個(gè)資源都有一個(gè)默認(rèn)的優(yōu)先級(jí),這個(gè)優(yōu)先級(jí)做為初始化值

  1. ResourceLoadPriority priority = TypeToPriority(type); 

不同類型的資源優(yōu)先級(jí)是這么定義的:

  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; 

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

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

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

瀏覽器空閑的時(shí)候就會(huì)去加載。另外還可以預(yù)解析DNS:

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

預(yù)建立TCP連接:  

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

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

注意上面的switch-case設(shè)定資源優(yōu)先級(jí)有一個(gè)順序,如果既是script又是prefetch的話得到的優(yōu)化級(jí)是high,而不是prefetch的very low,因?yàn)閜refetch是最后一個(gè)判斷。所以在設(shè)定了資源默認(rèn)的優(yōu)先級(jí)之后,會(huì)再對(duì)一些情況做一些調(diào)整,主要是對(duì)prefetch/preload的資源。包括:

a)降低preload的字體的優(yōu)先級(jí)

如下代碼:

  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; 

會(huì)把預(yù)加載字體的優(yōu)先級(jí)從very-high變成high

b)降低defer/async的script的優(yōu)先級(jí)

如下代碼:

  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的話,那么它優(yōu)先級(jí)會(huì)變成最低。

4)頁面底部preload的script優(yōu)先級(jí)變成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那么優(yōu)先級(jí)調(diào)成最低(上面第3小點(diǎn)),否則如果是preload的script,并且如果頁面已經(jīng)加載了一張圖片就認(rèn)為這個(gè)script是在頁面偏底部的位置,就把它的優(yōu)先級(jí)調(diào)成medium。通過一個(gè)flag決定是否已經(jīng)加載過第一張圖片了:

  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的圖片前認(rèn)為是early,而在后面認(rèn)為是late,late的script的優(yōu)先級(jí)會(huì)偏低。

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

5)把同步即堵塞加載的資源的優(yōu)先級(jí)調(diào)成最高

如下代碼:

  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對(duì)象里面的優(yōu)先最級(jí)是最高的,所以本來是hight的ajax同步請(qǐng)求在最后return的時(shí)候會(huì)變成very-high。

這里是取了兩個(gè)值的最大值,第一個(gè)值是上面進(jìn)行各種判斷得到的priority,第二個(gè)在初始這個(gè)ResourceRequest對(duì)象本身就有的一個(gè)優(yōu)先級(jí)屬性,返回最大值后再重新設(shè)置resource_request的優(yōu)先級(jí)屬性。

在構(gòu)建resource request對(duì)象時(shí)所有資源都是最低的,這個(gè)可以從構(gòu)造函數(shù)里面知道:

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

但是同步請(qǐng)求在初始化的時(shí)候會(huì)先設(shè)置成最高:

  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; 

以上就是基本的資源加載優(yōu)先級(jí)策略。

(2)轉(zhuǎn)換成Net的優(yōu)先級(jí)

這個(gè)是在渲染線程里面進(jìn)行的,上面提到的資源優(yōu)先級(jí)在發(fā)請(qǐng)求之前會(huì)被轉(zhuǎn)化成Net的優(yōu)先級(jí):

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

資源優(yōu)先級(jí)對(duì)應(yīng)Net的優(yōu)先級(jí)關(guān)系如下所示:

畫成一個(gè)表:

Net Priority是請(qǐng)求資源的時(shí)候使用的,這個(gè)是在Chrome的IO線程里面進(jìn)行的, 我在《JS與多線程》的Chrome的多線程模型里面提到,每個(gè)頁面都有Renderer線程負(fù)責(zé)渲染頁面,而瀏覽器有IO線程,用來負(fù)責(zé)請(qǐng)求資源等。為什么IO線程不是放在每個(gè)頁面里面而是放在瀏覽器框架呢?因?yàn)檫@樣的好處是如果兩個(gè)頁面請(qǐng)求了相同資源的話,如果有緩存的話就能避免重復(fù)請(qǐng)求了。

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

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

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

 

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

 

之前是使用Attach to Process把渲染進(jìn)程的PID傳進(jìn)來,因?yàn)槊總€(gè)頁面都是獨(dú)立的一個(gè)進(jìn)程,現(xiàn)在要改成debug Chromium進(jìn)程。然后在content/browser/loader/resource_scheduler.cc這個(gè)文件里的ShouldStartRequest函數(shù)里面打個(gè)斷點(diǎn),接著在Chromium里面打開一個(gè)網(wǎng)頁,就可以看到斷點(diǎn)生效了。在XCode里面可以看到當(dāng)前線程名稱叫Chrome_IOThread:

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

(3)資源加載

上面提到的ShouldStartRequest這個(gè)函數(shù)是判斷當(dāng)前資源是否能開始加載了,如果能的話就準(zhǔn)備加載了,如果不能的話就繼續(xù)把它放到pending request隊(duì)列里面,如下代碼所示:

  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的加載資源消息就會(huì)調(diào)上面的ScheduleRequest函數(shù),除了收到消息之外,還有一個(gè)地方也會(huì)調(diào)用:

  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.  } 

這個(gè)函數(shù)的特點(diǎn)是遍歷pending requests,每次取出優(yōu)先級(jí)最高的一個(gè)request,然后調(diào)ShouldStartRequest判斷是否能運(yùn)行了,如果能的話就把它從pending requests里面刪掉,然后運(yùn)行。

而這個(gè)函數(shù)會(huì)有三個(gè)地方會(huì)調(diào)用,一個(gè)是IO線程的循環(huán)判斷,只要還有未完成的任務(wù),就會(huì)觸發(fā)加載,第二個(gè)是當(dāng)有請(qǐng)求完成時(shí)會(huì)調(diào),第三個(gè)是要插入body標(biāo)簽的時(shí)候。所以主要總共有三個(gè)地方會(huì)觸發(fā)加載:

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

(2)每個(gè)請(qǐng)求完成之后,觸發(fā)加載pending requests里還未加載的請(qǐng)求

(3)IO線程定時(shí)循環(huán)未完成的任務(wù),觸發(fā)加載

  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> 

知道了觸發(fā)加載機(jī)制之的,接著研究具體優(yōu)先加載的過程,用以下html做為demo:

然后把Chrome的網(wǎng)絡(luò)速度調(diào)為Fast 3G,讓加載速度降低,以便更好地觀察這個(gè)過程,結(jié)果如下圖所示:

從上圖可以發(fā)現(xiàn)以下特點(diǎn):

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

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

(3)JS比圖片優(yōu)先加載,即使出現(xiàn)得比圖片晚

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

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

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

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

首先認(rèn)清幾個(gè)概念,請(qǐng)求可分為delayable和none-delayable兩種:

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

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

還有一種是layout-blocking的請(qǐng)求:

  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; 

這是當(dāng)還沒有渲染body標(biāo)簽,并且優(yōu)先級(jí)在Medium之上的如CSS的請(qǐng)求。

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

  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)高優(yōu)先級(jí)的資源(>=Medium)、同步請(qǐng)求和非http(s)的請(qǐng)求能夠立刻加載

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

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

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

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

 

白色條是指queue的時(shí)間段,而灰色的是已經(jīng)in flight了但受到同域只能最多只能建立6個(gè)TCP連接等的影響而進(jìn)入的stalled狀態(tài),綠色是TTFB(Time to First Byte)從開始建立TCP連接到收到第一個(gè)字節(jié)的時(shí)間,藍(lán)色是下載的時(shí)間。

我們已經(jīng)解釋了大部分加載的特點(diǎn)的原因,對(duì)著上面那張圖可以再重述一次:

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

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

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

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

  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的優(yōu)先級(jí)一開始是Low的,即是delayable的,但是后面又變成了Medium就不是delayable了,是high priority,為什么它的優(yōu)先級(jí)能夠提高呢?一開始是Low是因?yàn)樗峭茰y(cè)加載的,所以是優(yōu)先級(jí)比較低,但是當(dāng)DOM構(gòu)建到那里的時(shí)候它就不是preload的,變成正常的JS加載了,所以它的優(yōu)先級(jí)變成了Medium,這個(gè)可以從has_html_body標(biāo)簽進(jìn)行推測(cè),而2.js要等到1.js下載和解析完,它能算是正常加載,否則還是推測(cè)加載,因此它的優(yōu)先級(jí)沒有得到提高。

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

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

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

(2)優(yōu)先級(jí)是如何動(dòng)態(tài)改變的?

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

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

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

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

獵豹瀏覽瀏覽器

2009-11-26 10:55:41

2010-01-28 10:13:43

2020-05-15 15:23:25

Chrome瀏覽器谷歌

2015-01-21 15:45:50

斯巴達(dá)瀏覽器

2018-02-02 15:48:47

ChromeDNS解析

2021-01-07 07:52:04

瀏覽器網(wǎng)頁資源加載

2009-12-06 09:38:02

Chrome瀏覽器Avast

2009-03-07 09:57:41

Realplayer捆綁Chrome

2009-12-03 10:56:34

谷歌Chrome瀏覽器

2010-01-10 17:50:17

2009-09-22 09:17:46

谷歌Chrome瀏覽器

2012-08-08 09:18:47

Chrome瀏覽器

2009-07-17 09:16:20

Google Chro瀏覽器操作系統(tǒng)

2019-02-15 15:15:59

ChromeJavascriptHtml

2013-11-13 15:54:20

Chrome 31瀏覽器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 99在线免费观看视频 | 久草.com| 国产成人av在线 | 久久综合99| 美女爽到呻吟久久久久 | 欧美一区二区三区国产 | 日本免费一区二区三区四区 | 欧美黄色小视频 | 97人人爱| 中文在线一区二区 | 美女视频一区二区三区 | 天天躁日日躁xxxxaaaa | 91在线播 | 国产精品美女久久久久aⅴ国产馆 | 一区二区在线免费观看 | 国产在线观看一区二区 | 不卡视频一区二区三区 | 日韩国产中文字幕 | 在线播放一区二区三区 | 一级片免费视频 | 大象视频一区二区 | 99热这里都是精品 | 一区二区三区不卡视频 | 综合亚洲视频 | 97精品国产97久久久久久免费 | 日本精品一区二区三区在线观看 | 免费精品视频 | 国产精品国产亚洲精品看不卡15 | 91看片在线 | 一区二区三区在线播放 | 美女一区二区在线观看 | 亚洲www啪成人一区二区 | 亚洲第一网站 | 91精品久久久久久久久久入口 | www.久草.com | 欧洲精品视频一区 | 亚州无限乱码 | 国产精品久久久久久久久婷婷 | 特黄色一级毛片 | 成年免费大片黄在线观看一级 | 一区二区高清不卡 |