如果你不喜欢 Dash 的网页反色


#1

Dash 不多介绍,虽然他很多 bug 和安全漏洞,重度依赖这货查文档。适配 mac Mojave 新推的黑色模式的方式不敢恭维,如下图所示,会将网页(除图片外)整个反色处理。也没见 Safari 自作聪明把网页反色了啊。

这种机械反色表面上一看还算和谐,其实在大量文字(特别还是非母语)的时候就觉得对比度不合理影响阅读。

简单分析了一下,Dash 实现 WebView 反色是在整个 View 上加了一层滤镜:


void __cdecl -[DHWebView setDarkModeFiltersIfNeeded](DHWebView *self, SEL a2)
{
  void *v2; // rax
  void *v3; // rax
  void *v4; // rax
  const __CFString *v5; // [rsp+0h] [rbp-50h]
  void *v6; // [rsp+8h] [rbp-48h]
  id v7; // [rsp+10h] [rbp-40h]
  void *v8; // [rsp+18h] [rbp-38h]
  __int64 v9; // [rsp+20h] [rbp-30h]

  if ( (unsigned __int8)+[DHAnnoManager isDark](&OBJC_CLASS___DHAnnoManager, "isDark")
    && (unsigned __int8)+[DHSystemVersionChecker _isSierra](&OBJC_CLASS___DHSystemVersionChecker, "_isSierra") )
  {
    v2 = objc_msgSend(self, "contentFilters");
    if ( !objc_msgSend(v2, "count") )
    {
      v7 = ((id (__cdecl *)(DHWebView_meta *, SEL))objc_msgSend)(
             (DHWebView_meta *)&OBJC_CLASS___DHWebView,
             "cubeFilter");
      v5 = CFSTR("inputContrast");
      v6 = objc_msgSend(&OBJC_CLASS___NSNumber, "numberWithDouble:", 0.95, CFSTR("inputContrast"));
      v3 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObjects:forKeys:count:", &v6, &v5, 1LL);
      v8 = objc_msgSend(&OBJC_CLASS___CIFilter, "filterWithName:withInputParameters:", CFSTR("CIColorControls"), v3);
      v4 = objc_msgSend(&OBJC_CLASS___NSArray, "arrayWithObjects:count:", &v7, 2LL);
      objc_msgSend(self, "setContentFilters:", v4);
    }
  }
  else if ( __stack_chk_guard == v9 )
  {
    objc_msgSend(self, "setContentFilters:", 0LL);
  }
}

这个反色非常暴力,可以看到 WebView 的审查元素功能也被反色了

这个很简单,直接在 setContentFilters 方法上加一个 hook 就好了。

第二处处理是使用 WebKit 添加用户 css,在所有图片上加了一层 invert css 滤镜:

54

此处代码质量真的不敢恭维(Dash 的 iOS 版本是开源的,可以去观摩一下什么叫烂代码,基本的循环什么都是不存在的,大段大段地 repeat yourself)

这里做自动化 patch 太麻烦了,最后我选择了在 setUserStyleSheetLocation 加载 css 之前打开这个文件,把含有 invert() 的行过滤掉(其实这里需要一个 css parser,或者在 WebView 里用 javascript 匹配到这个规则然后移除掉,太懒了)

代码抄袭了一部分 @Zhang 的某项目

// clang -shared -undefined dynamic_lookup -o /Applications/Dash.app/Contents/MacOS/libDash.dylib Dash.m
// optool install -c load -p @executable_path/libDash.dylib -t /Applications/Dash.app/Contents/MacOS/Dash

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

static void pleasedontinvertwebview(/* we don't care about the args */) {
  NSLog(@"oops");
}

typedef void (*OriginalSetUserCSSImp)(id self, SEL sel, NSURL *url);
static OriginalSetUserCSSImp originalImp;

static void pleasedontinvertimages(id self, SEL sel, NSURL *url) {
  NSLog(@"oops");
  NSError *err = nil;
  NSString *content = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err];
  if (err) return;
  NSArray *lines = [content componentsSeparatedByString:@"\n"];
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF contains[c] 'invert()')"];
  NSArray *filtered = [lines filteredArrayUsingPredicate:predicate];
  NSString *style = [filtered componentsJoinedByString:@"\n"];
  [style writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:nil];
  originalImp(self, sel, url);
}

__attribute__((constructor)) static void webview() {
  Method m = class_getInstanceMethod(NSClassFromString(@"DHWebView"),
                                     NSSelectorFromString(@"setContentFilters:"));
  if (m) method_setImplementation(m, (IMP)pleasedontinvertwebview);
  m = class_getInstanceMethod(NSClassFromString(@"WebPreferences"),
                                     NSSelectorFromString(@"setUserStyleSheetLocation:"));
  if (m) {
    originalImp = (OriginalSetUserCSSImp)method_getImplementation(m);
    method_setImplementation(m, (IMP)pleasedontinvertimages);
  }
}

#2

patch 之后如下


#3

很多出色产品的代码都很一般,这个背后说明了什么道理,值得我们深思