Skip to content

Chrome extension 修改 window.navigator.userAgent

网页开发有一些场景会做一些ua的判断,特别是在手机端展示的页面,非手机就不让看了,这还是挺让人觉得不方便的,毕竟在电脑上各种屏蔽广告的插件还是很强大的。

那么有没有办法假装电脑版的网页是手机设备呢。毕竟之前做过的项目一般来说是判断一下 window.navigator.userAgent 来判定当前是否是手机设备。然后再判断是否要显示,或者判断是否要支持一些特殊的功能。

正常情况下以本机为例,获取到的 window.navigator.userAgent 如下: 请添加图片描述 但是当我直接设置他的时候,他好像没反应,没有设置上去。 请添加图片描述

一个比较正式的文档https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent上说。

Navigator: userAgent property The Navigator.userAgent read-only property returns the user agent string for the current browser.

但是还是想改一下啊,这样就能在电脑上调试一些东西了,就比较方便。所以不管他,继续做。

找到一个比较好的的做法是,可以给navigator在设置一个get方法,然后用户再次获取的时候就是调用的自定义的了。方法如下:

javascript
function setUserAgent(window, userAgent) {
  if (window.navigator.userAgent != userAgent) {
    var userAgentProp = {
      get: function() {
        return userAgent;
      }
    };
    try {
      Object.defineProperty(window.navigator, 'userAgent', userAgentProp);
      console.log('change ua success ', window, window.navigator.userAgent)
    } catch (e) {
      console.log('change ua fail ', e)
      window.navigator = Object.create(navigator, {
        userAgent: userAgentProp
      });
    }
  }
}

这个方法可以直接设置指定window的useragent,当有iframe修改需求的时候也是可以用的,还是比较贴心的。

使用方式如下:

javascript
setUserAgent(
  window, 
  'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/15.0.0 Mobile/15A5370a Safari/602.1'
);

下面在开发者工具上试一下: 请添加图片描述

看样子是可以了,那么下边就是怎么把这个结论应用到我们自己的插件当中了。

下边有两种方式可以实现这个效果,都是同 ExecutionWorld 这个关键的东西相关。默认的脚本都是在隔离环境中运行的,运行的局部变量同网页中的变量隔离,如果在之前的脚本中设置不会生效。尝试了很久都是血和泪。

content_scripts 方式

在 mainfest 中增加如下配置:

json
  "content_scripts": [
    {
      "matches": [ "http://*/*", "https://*/*"],
      "run_at": "document_start",
      "js": ["js/content_scripts.js"],
      "css": ["js/style.css"],
      "world": "MAIN"
    }
  ]

这个 world: "MAIN" 很关键。文档地址https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world

然后在 content_script 文件中增加如下代码:

javascript
function setUserAgent(window, userAgent) {
  if (window.navigator.userAgent != userAgent) {
    var userAgentProp = {
      get: function() {
        return userAgent;
      }
    };
    try {
      Object.defineProperty(window.navigator, 'userAgent', userAgentProp);
    } catch (e) {
      window.navigator = Object.create(navigator, {
        userAgent: userAgentProp
      });
    }
  }
}

setUserAgent(
  window, 
  'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/15.0.0 Mobile/15A5370a Safari/602.1'
);

但是这种实现方式有个缺点就是在 content_script 文件中不能添加 chrome.runtime.onMessage.addListener 了,这样就不好控制脚本什么时候执行什么时候不知行了,会导致污染所有的网页。下边就是第二种方法。

scripting

还有个神奇的方法,可以动态的在可控的tab里边执行一些代码。 文档https://developer.chrome.com/docs/extensions/reference/api/scripting

能够根据tabid控制影响范围,还是非常好的。 实现方式如下: 增加权限

json
  "background": {
    "service_worker": "js/background.js"
  },
  "permissions": [
    "scripting", 
    "activeTab"
  ],

在background.js 文件中的合适位置上增加如下代码:

javascript
chrome.scripting.executeScript({
    world: 'MAIN',
    target: {tabId: tabId},
    func: () => {
        function setUserAgent(window, userAgent) {
            if (window.navigator.userAgent != userAgent) {
              var userAgentProp = {
                get: function() {
                  return userAgent;
                }
              };
              try {
                Object.defineProperty(window.navigator, 'userAgent', userAgentProp);
              } catch (e) {
                window.navigator = Object.create(navigator, {
                  userAgent: userAgentProp
                });
              }
            }
          }
          
          setUserAgent(
            window, 
            'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/15.0.0 Mobile/15A5370a Safari/602.1'
          );
    }
})

tabId 是指定的一个活动的tab或者特定的tab。 另外这个 world: 'MAIN', 一定要添加上,要不然不会生效。

请添加图片描述

如上例子就是监听插件按钮被点击的时候,background 的 service_worker 修改当前活动tab的 user agent。