chromium lazyimages 介绍

原地址 https://lulus.life/2018/08/24/lazyimage/

首先通过视频看一下效果
视频地址

视频解释

这个功能大概就是,当图片快滑到视图之内的时候,再去请求加载图片

主要需要注意以下几点

  • 页面刚加载的时候图片在viewport之外, 这时图片没有全部加载
  • 图片资源只发出了一个206请求,用来获取前 2048字节
  • 当滑动页面,致使图片靠近viewport的时候,浏览器发出请求,请求整个图片资源。

如何启用该功能

需要注意的事项

网站里的图片最好有 etag 或者 Date + Last Modified
原因:

chrmium 只会对于有etag 或者 Date - Last Modified > 60sec的资源建立sparse缓存, 这样的话,当图片离viewport比较远的时候,首先blink会发出一个带有range的请求,请求资源的0-2047个字节。 当滑动过程中图片即将进入viewport的时候,会再次发出一个请求请求整个图片。 如果没有sparse缓存的话, 第二个请求会请求一个完完整整的图片数据,当我们有了sparse缓存,第二个请求首先会先从sparse缓存里读取2048个字节,然后再从网络请求剩下的内容

是否创建sparse文件由以下函数决定

bool HttpUtil::HasStrongValidators(HttpVersion version,
                                   const std::string& etag_header,
                                   const std::string& last_modified_header,
                                   const std::string& date_header) {
  if (!HasValidators(version, etag_header, last_modified_header))
    return false;

  if (version < HttpVersion(1, 1))
    return false;

  if (!etag_header.empty()) {
    size_t slash = etag_header.find('/');
    if (slash == std::string::npos || slash == 0)
      return true;

    std::string::const_iterator i = etag_header.begin();
    std::string::const_iterator j = etag_header.begin() + slash;
    TrimLWS(&i, &j);
    if (!base::LowerCaseEqualsASCII(base::StringPiece(i, j), "w"))
      return true;
  }

  base::Time last_modified;
  if (!base::Time::FromString(last_modified_header.c_str(), &last_modified))
    return false;

  base::Time date;
  if (!base::Time::FromString(date_header.c_str(), &date))
    return false;

  // Last-Modified is implicitly weak unless it is at least 60 seconds before
  // the Date value.
  return ((date - last_modified).InSeconds() >= 60);
}

另外介绍一下 spase文件, 对于 有range的请求 chromium会把资源放到一个 {url的hash, 8个字节}_s文件里

举个例子:
首先我们清楚chrome的缓存

wanghui@wh:/tmp/chrome/Default/Cache$ ls
wanghui@wh:/tmp/chrome/Default/Cache$ 

可以看到我的缓存是空的。
然后创建以下页面

 <html>
 <head>
 <style type="text/css">
 #test {
   height:2000px;
 }
 </style>
 </head>
   <div id="test">
      lazy load image 
   </div>
   <img src="https://lulus.life/01.jpg" />
 </html>

用我们的chrome访问该页面

wanghui@wh:~/chromium/src$ out/Default/chrome --user-data-dir=/tmp/chrome --no-proxy-server --enable-quic --origin-to-force-quic-on=lulus.life:443 --host-resolver-rules='MAP lulus.life:443 127.0.0.1:6121' https://lulus.life/test.html

我用的quic服务器,忽略我的命令行
看一下我的缓存目录

wanghui@wh:/tmp/chrome/Default/Cache$ ls -l
total 28
-rw------- 1 wanghui wanghui 4049 Aug 24 18:39 235add38b243d26b_0
-rw------- 1 wanghui wanghui 2129 Aug 24 18:39 235add38b243d26b_s
-rw------- 1 wanghui wanghui 4213 Aug 24 18:39 8f505f077bd94c0e_0
-rw------- 1 wanghui wanghui 4001 Aug 24 18:39 bf9ed9cbda59708c_0
-rw------- 1 wanghui wanghui   24 Aug 24 18:39 index
drwx------ 2 wanghui wanghui 4096 Aug 24 18:39 index-dir
wanghui@wh:/tmp/chrome/Default/Cache$ 

打开 235add38b243d26b_s文件

 1 235add38b243d26b_s +                                                                                                                                                                                           X 
 00000000: 305c 72a7 1b6d fbfc 0800 0000 1900 0000  0\r..m..........
 00000010: 58ca eab6 0000 0000 6874 7470 733a 2f2f  X.......https://
 00000020: 6c75 6c75 732e 6c69 6665 2f30 312e 6a70  lulus.life/01.jp
 00000030: 676b 6753 6501 bf97 eb00 0000 0000 0000  gkgSe...........
 00000040: 0000 0800 0000 0000 005a 4219 8d00 0000  .........ZB.....
 00000050: 00ff d8ff e000 104a 4649 4600 0101 0100  .......JFIF.....
 00000060: 6000 6000 00ff db00 4300 0201 0102 0101  `.`.....C.......
 00000070: 0202 0202 0202 0202 0305 0303 0303 0306  ................
 00000080: 0404 0305 0706 0707 0706 0707 0809 0b09  ................
 00000090: 0808 0a08 0707 0a0d 0a0a 0b0c 0c0c 0c07  ................
 000000a0: 090e 0f0d 0c0e 0b0c 0c0c ffdb 0043 0102  .............C..
 000000b0: 0202 0303 0306 0303 060c 0807 080c 0c0c  ................
 000000c0: 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c  ................
 000000d0: 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c  ................
 000000e0: 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c 0c0c 0cff  ................
 000000f0: c000 1108 0154 00ff 0301 2200 0211 0103  .....T....".....
 00000100: 1101 ffc4 001f 0000 0105 0101 0101 0101  ................
 00000110: 0000 0000 0000 0000 0102 0304 0506 0708  ................
 00000120: 090a 0bff c400 b510 0002 0103 0302 0403  ................
 00000130: 0505 0404 0000 017d 0102 0300 0411 0512  .......}........
 00000140: 2131 4106 1351 6107 2271 1432 8191 a108  !1A..Qa."q.2....
 00000150: 2342 b1c1 1552 d1f0 2433 6272 8209 0a16  #B...R..$3br....
 00000160: 1718 191a 2526 2728 292a 3435 3637 3839  ....%&'()*456789
 00000170: 3a43 4445 4647 4849 4a53 5455 5657 5859  :CDEFGHIJSTUVWXY
 00000180: 5a63 6465 6667 6869 6a73 7475 7677 7879  Zcdefghijstuvwxy
 00000190: 7a83 8485 8687 8889 8a92 9394 9596 9798  z...............
 000001a0: 999a a2a3 a4a5 a6a7 a8a9 aab2 b3b4 b5b6  ................
 000001b0: b7b8 b9ba c2c3 c4c5 c6c7 c8c9 cad2 d3d4  ................
 000001c0: d5d6 d7d8 d9da e1e2 e3e4 e5e6 e7e8 e9ea  ................
 000001d0: f1f2 f3f4 f5f6 f7f8 f9fa ffc4 001f 0100  ................
 000001e0: 0301 0101 0101 0101 0101 0000 0000 0000  ................
 000001f0: 0102 0304 0506 0708 090a 0bff c400 b511  ................
 00000200: 0002 0102 0404 0304 0705 0404 0001 0277  ...............w

通过上一篇 介绍http cache的文章我们可以知道,这个数据属于
https://lulus.life/01.jpg
只不过是一部分数据

接下来我们滑动窗口,让图片进入viewport
再次看一下cache目录

wanghui@wh:/tmp/chrome/Default/Cache$ ls -l
total 56
-rw------- 1 wanghui wanghui  4049 Aug 24 18:43 235add38b243d26b_0
-rw------- 1 wanghui wanghui 29164 Aug 24 18:43 235add38b243d26b_s
-rw------- 1 wanghui wanghui  4213 Aug 24 18:43 8f505f077bd94c0e_0
-rw------- 1 wanghui wanghui  4001 Aug 24 18:43 bf9ed9cbda59708c_0
-rw------- 1 wanghui wanghui    24 Aug 24 18:43 index
drwx------ 2 wanghui wanghui  4096 Aug 24 18:43 index-dir
wanghui@wh:/tmp/chrome/Default/Cache$ 

可以看到235add38b243d26b_s 文件大小涨到了29164,也就是图片的大小。

LazyImage 相关代码

LazyImage相关提交关联这个Issule 846170 (需要翻墙)

如果开启该功能后,所有图片会预先加载前0-2047字节, 并创建一个图片作为placeholder。

看一个堆栈

void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior,
                                      UpdateFromElementBehavior update_behavior,
                                      const KURL& url,
                                      ReferrerPolicy referrer_policy,
                                      UpdateType update_type) {
    ...

    FetchParameters params(resource_request, resource_loader_options);
    ConfigureRequest(params, bypass_behavior, *element_,
                     document.GetFrame()->GetClientHintsPreferences());

    if (update_behavior != kUpdateForcedReload &&
        lazy_image_load_state_ == LazyImageLoadState::kNone) {
      const auto* frame = document.GetFrame();
      if (frame->IsClientLoFiAllowed(params.GetResourceRequest())) {
        params.SetClientLoFiPlaceholder();
      } else if (auto* html_image = ToHTMLImageElementOrNull(GetElement())) {
        if (html_image->ElementCreatedByParser() &&
            frame->IsLazyLoadingImageAllowed()) {
          params.SetAllowImagePlaceholder();
          lazy_image_load_state_ = LazyImageLoadState::kDeferred;
        }
      }
    }

当允许lazyload的时候设置params为 AllowImagePlaceholder

bool LocalFrame::IsLazyLoadingImageAllowed() const {
  if (!RuntimeEnabledFeatures::LazyImageLoadingEnabled())
    return false;
  if (Owner() && !Owner()->ShouldLazyLoadChildren())
    return false;
  return true;
}

所以Image会先以imageholder的方式呈现。imageholder的加载流程下次分析, imageholder是根据图片前0-2047字节创建的一个图片, 前2048字节一般包含图片的header,从此可以解析出图片的宽高以及tumbnail等信息

lazyimage主要的类是LazyLoadImageObserver,由这个类来控制是否需要加载整个图片。
继续看看堆栈

DoUpdateFromElement接下来会调用UpdateImageState, 而UpdateImageState会调用LazyLoadImageObserver的静态函数
StartMonitoring, 我们看一下这个函数

void LazyLoadImageObserver::StartMonitoring(Element* element) {
  if (LocalFrame* frame = element->GetDocument().GetFrame()) {
    if (Document* document = frame->LocalFrameRoot().GetDocument()) {
      document->EnsureLazyLoadImageObserver()
          .lazy_load_intersection_observer_->observe(element);
    }
  }
}

可以看到该函数首先会调用document的EnsureLazyLoadImageObserver

LazyLoadImageObserver& Document::EnsureLazyLoadImageObserver() {
  if (!lazy_load_image_observer_)
    lazy_load_image_observer_ = new LazyLoadImageObserver(*this);
  return *lazy_load_image_observer_;
}

EnsureLazyLoadImageOberser判断如果当前document没有创建LazyLoadImageObserver就会创建一个LazyLoadImageObserver赋值给成员变量 lazy_load_image_observer_.

接下来看一下LazyLoadImageObserver的构造函数
这个LazyLoadImageObserver会创建一个IntersectionObserver 代码在 third_party/blink/renderer/core/html/lazy_load_image_observer.cc

LazyLoadImageObserver::LazyLoadImageObserver(Document& document) {
  DCHECK(RuntimeEnabledFeatures::LazyImageLoadingEnabled());
  document.AddConsoleMessage(ConsoleMessage::Create(
      kInterventionMessageSource, kInfoMessageLevel,
      "Images loaded lazily and replaced with placeholders. Load events are "
      "deferred. See https://crbug.com/846170"));
  lazy_load_intersection_observer_ = IntersectionObserver::Create(
      {Length(kLazyLoadRootMarginPx, kFixed)},
      {std::numeric_limits<float>::min()}, &document,
      WTF::BindRepeating(&LazyLoadImageObserver::LoadIfNearViewport,
                         WrapWeakPersistent(this)));
}

其中 kLazyLoadRootMarginPx = 800

我们先看一下IntersectionObserver的构造函数

IntersectionObserver::IntersectionObserver(
    IntersectionObserverDelegate& delegate,
    Element* root,
    const Vector<Length>& root_margin,
    const Vector<float>& thresholds,
    bool track_visibility)
    : ContextClient(delegate.GetExecutionContext()),
      delegate_(&delegate),
      root_(root),
      thresholds_(thresholds),
      top_margin_(kFixed),
      right_margin_(kFixed),
      bottom_margin_(kFixed),
      left_margin_(kFixed),
      last_run_time_(-s_v2_throttle_delay_),
      root_is_implicit_(root ? 0 : 1),
      track_visibility_(track_visibility ? 1 : 0) {

  ...

  if (Document* document = TrackingDocument())
    document->EnsureIntersectionObserverController().AddTrackedObserver(*this);
}

可以看到这个observer加到了document的observercontroller里
再看一个堆栈

当页面viewport有更新的时候会计算observe的元素,是否符合要求,如果符合会返回给observer所有符合条件的element。
接下来就会调用 LazyLoadImageObserver::LoadIfNearViewport

void LazyLoadImageObserver::LoadIfNearViewport(
    const HeapVector<Member<IntersectionObserverEntry>>& entries) {
  DCHECK(!entries.IsEmpty());

  for (auto entry : entries) {
    if (!entry->isIntersecting())
      continue;
    Element* element = entry->target();
    if (auto* image_element = ToHTMLImageElementOrNull(element))
      image_element->LoadDeferredImage();

    // Load the background image if the element has one deferred.
    if (const ComputedStyle* style = element->GetComputedStyle())
      style->LoadDeferredImages(element->GetDocument());

    lazy_load_intersection_observer_->unobserve(element);
  }
}

然后就是请求整个图片的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值