你能否通过脚本确定Chrome是否处于隐身模式?

138

是否有可能通过脚本确定Google Chrome是否处于隐身模式?

编辑:实际上我是想知道是否可以通过用户脚本来确定,但是答案假设JavaScript在网页上运行。我已经在这里重新提出了问题(链接),关于用户脚本的问题。


1
等一下,我刚意识到我没有表述清楚——我是指使用用户脚本。我会保留这个问题,因为它有一些有用的回答(谢谢),但我会提出另一个带有额外澄清的问题。 - RodeoClown
1
https://dev59.com/U3E85IYBdhLWcg3wRBRU - Josh Lee
@aren 为什么不让脚本更容易地确定是否在隐身/私密模式下查看?页面仍在浏览器沙盒中执行,理论上无法规避页面访问的私密性,但程序员可以通过知道它是隐身模式来增加价值。我有什么遗漏吗? - Pete Alvin
1
@PeteAlvin 或者该网站可以通过检测隐身模式来减少价值。波士顿环球报的网站不会在隐身模式下显示其文章,防止用户规避其免费文章配额。 总的来说,我更喜欢一个网站了解我较少。 - JohnC
1
Chrome还将努力解决任何其他当前或未来的隐身模式检测方法。因此,任何解决方案都可能在未来某个时候停止工作。 - sudoqux
显示剩余6条评论
11个回答

253

这个答案的功能取决于Chrome的版本。最近的评论是在v90上运行正常。

是的,文件系统API在隐身模式下被禁用。当您处于隐身模式和非隐身模式时,请查看https://jsfiddle.net/w49x9f1a/

示例代码:

    var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    if (!fs) {
      console.log("check failed?");
    } else {
      fs(window.TEMPORARY,
         100,
         console.log.bind(console, "not in incognito mode"),
         console.log.bind(console, "incognito mode"));
    }


2
@user2718671 在我的最新版Chrome浏览器上的mac osx系统中,http://jsfiddle.net/w49x9f1a/ 仍然可以正常工作。很奇怪... - Alok
1
这个不再起作用了(也许过去它曾经有效)。 - Alexandru R
1
我在Chrome上尝试了一下,它可以工作,但逻辑是相反的。 - Lucas Massuh
11
由于这些提交,这个问题很快就会被解决。 - blueren
13
Chrome浏览器版本77.0.3865.90中出现故障。 - Gitesh Purbia
显示剩余24条评论

24
在Chrome 74到84.0.4147.135中,您可以通过估计可用的文件系统存储空间来确定此内容。
请查看jsfiddle
if ('storage' in navigator && 'estimate' in navigator.storage) {
    const {usage, quota} = await navigator.storage.estimate();
    console.log(`Using ${usage} out of ${quota} bytes.`);

    if(quota < 120000000){
        console.log('Incognito')
    } else {
        console.log('Not Incognito')
    }   
} else {
    console.log('Can not detect')
}

1
很遗憾,现在这个不起作用:例如,在最新的微软EDGE浏览器中,我在所谓的私密模式下有224053418字节。在Chrome中,我在隐身模式下看到234549212字节。 - djdance

20

一种方法是访问唯一的URL,然后检查CSS是否将指向该URL的链接视为已访问。

你可以在“探测无痕模式”(链接失效)中看到一个例子。

同一作者的研究论文,用来替换上面的“探测无痕模式”链接

main.html中添加一个iframe,

 <iframe id='testFrame' name='testFrame' onload='setUniqueSource(this)' src='' style="width:0; height:0; visibility:hidden;"></iframe>

以及一些 JavaScript 代码:

function checkResult() {
  var a = frames[0].document.getElementById('test');
  if (!a) return;

  var color;
  if (a.currentStyle) {
    color = a.currentStyle.color;
  } else {
    color = frames[0].getComputedStyle(a, '').color;
  }

  var visited = (color == 'rgb(51, 102, 160)' || color == '#3366a0');
  alert('mode is ' + (visited ? 'NOT Private' : 'Private'));
}

function setUniqueSource(frame) {
  frame.src = "test.html?" + Math.random();
  frame.onload = '';
}

然后在加载到 iFrame 中的 test.html 文件中:

<style> 
   a:link { color: #336699; }
   a:visited { color: #3366A0; }
</style> 
<script> 
  setTimeout(function() {
    var a = document.createElement('a');
    a.href = location;
    a.id = 'test';
    document.body.appendChild(a);
    parent.checkResult();
  }, 100);
</script> 

注意: 从文件系统尝试此操作可能会导致 Chrome 产生"不安全的JavaScript"警告。但是,如果通过web服务器进行服务,则可以正常工作。


挺不错的,我没意识到隐身模式不会突出显示已访问的链接。不过这需要用户点击链接。 - Igor Zevaka
1
不,它不会,iframe会“点击”链接。 - kibibu
42
大多数浏览器并不通过 JavaScript 公开 :visited 样式信息,所以这实际上是不起作用的。CSS 接口会显示链接具有非访问颜色。这是一项安全措施,至少从 2010 年起在 WebKit 和 Gecko 浏览器中实施。目的是保护用户的历史记录(例如,有人可以尝试所有可能的 URL,并将访问过的 URL 发送给第三方,从而获取 URL 中的令牌等信息)。 - Timo Tijhof
3
Mozilla官方文章解释:visited相关的隐私变更:https://hacks.mozilla.org/2010/03/privacy-related-changes-coming-to-css-vistited/ - Denilson Sá Maia

7
你可以使用JavaScript查看JHurrah的答案。除了不突出显示链接之外,隐身模式只是不保存浏览历史和Cookie。来自Google 帮助页面

  • 在隐身模式下打开的网页和下载的文件不会记录在您的浏览和下载历史中。
  • 关闭您打开的所有隐身窗口后,所有新的Cookie都会被删除。

正如你所看到的,在普通浏览和隐身模式之间的差异发生在你访问网页之后,因此当浏览器处于这种模式时,它与服务器之间没有任何通信。

您可以使用许多HTTP请求分析器之一(例如此处的)查看浏览器向服务器发送的确切内容。比较普通会话和隐身会话之间的标头,您将看不到任何区别。


最近,它禁用了除用户明确标记为隐身安全的扩展之外的所有扩展。 - oxygen

5
如果您正在开发扩展程序,那么可以使用选项卡API来确定窗口/标签是否为隐身模式。
更多信息请参见此处
如果您只是在处理网页,则不容易实现,并且它是有意设计成这样。但是,我注意到在隐身模式下尝试打开数据库(window.database)的所有尝试均会失败,这是因为在隐身模式下不允许留下任何数据痕迹在用户机器上。
我没有测试过,但我怀疑所有对localStorage的调用也都会失败。

5
针对寻找解决方案的人,以下是截至2021年10月各种浏览器检测私密浏览模式的简要介绍:
  • Chromium:类似于Vinnie James的答案,调用navigator.storage.estimate(),获取quota属性并将其与performance.memory.jsHeapSizeLimit进行比较。如果quota属性小于jsHeapSizeLimit,则为隐身模式。如果jsHeapSizeLimit未定义,则使用1073741824(1 GiB)。

  • Safari for macOS:在不存在的推送服务器上使用safari.pushNotification.requestPermission,并获取错误。如果错误中没有出现“gesture”,则处于私密模式。

  • Safari for iOS:创建一个iframe,并使用contentWindow.applicationCache添加错误事件监听器。如果错误触发,则为私密模式。

  • Firefox:在私密窗口中,navigator.serviceWorker将未定义。

  • Internet Explorer:在InPrivate模式下,window.indexedDB将未定义。

你可以在我在GitHub上提供的detectIncognito脚本中看到这些方法的实现。


2

更新这似乎不再起作用了


这里使用了promise来等待异步代码设置标志,以便我们可以在后续同步使用它。

let isIncognito = await new Promise((resolve, reject)=>{
    var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    if (!fs) reject('Check incognito failed');
    else fs(window.TEMPORARY, 100, ()=>resolve(false), ()=>resolve(true));      
});

那么我们可以这样做:
if(isIncognito) alert('in incognito');
else alert('not in incognito');

注意,要使用await,您需要在异步函数内部。如果不是,请将所有代码都包装在其中以便能够使用。


这看起来非常有趣,是一种我不熟悉的Javascript形式,并且似乎在我的Windows 10上的Netbeans 8.2 IDE中不受支持。但如果我能让它工作,我也很好奇:如果我想让isIncognito成为一个只能为true或false的布尔值,我能否用resolve(false)替换reject('Check incognito failed')?谢谢。 - Ryan
3
我看到了“语法错误:await是一个保留字”的提示,我认为await关键字必须放在一个async函数内部才能正常使用。因此,我认为这段代码不会起作用。 - Ryan
1
如果调用reject,则会引发异常并且不会返回值,因此根据代码编写方式,isIncognito始终为truefalse。但是,如果您想要返回一个值,也可以使用“resolve”。这可能取决于环境如何处理全局范围内的等待?它在全局范围内的Chrome控制台上运行,但您可以尝试将所有内容包装在(async function() { 'everything here' })();中,以便在异步函数内运行。 - aljgom
3
或者,你可以将承诺保存到isIncognito而不是结果,然后在异步函数中运行它:let isIncognito = new Promise(...etc),然后在其他地方你可以有一个函数,例如(async function() { if( !(await isIncognito) ) {alert('not incognito') } })(); - aljgom
2
此答案在 Chrome 76+ 中被无效化。 - Cruncher
不起作用。 - Pioz

0

这是建议的答案,使用ES6语法编写并稍微整理了一下。

const isIncognito = () => new Promise((resolve, reject) => {
    const fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    
    if (!fs) {
        reject('Cant determine whether browser is running in incognito mode!');
    }

    fs(window.TEMPORARY, 100, resolve.bind(null, false), resolve.bind(null, true));
});

// Usage
isIncognito()
    .then(console.log)
    .catch(console.error)

在Chrome的无痕模式下,这将返回false。 - Crashalot

0

基于Alok的答案的快速函数(注意:这是异步的)

更新 - 不再起作用

function ifIncognito(incog,func){
    var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
    if (!fs)  console.log("checking incognito failed");
    else {
        if(incog) fs(window.TEMPORARY, 100, ()=>{}, func);
        else      fs(window.TEMPORARY, 100, func, ()=>{});
    }
} 

用法:

ifIncognito(true,  ()=>{ alert('in incognito') });
// or
ifIncognito(false, ()=>{ alert('not in incognito') });

0

其他答案似乎在最近的Chrome版本中已经不再有效。

这个想法是找出存储估计值,以确定标签是否为隐身模式。隐身标签的存储大小较小。

在普通窗口和隐身窗口中运行此代码,并记录配额大小。

const {quota} = await navigator.storage.estimate();
console.log(quota);

使用配额大小差异来实现隐身模式检测的逻辑。


以下逻辑适用于Chrome v105

const { quota } = await navigator.storage.estimate();

if (quota.toString().length === 10) {
  console.log("gotcha: this is incognito tab"); //quota = 1102885027
} else {
  console.log("this is a normal tab"); //quota = 296630877388
}

此外,也可以查看这个解决方案以获得更广泛的支持(同时包括其他浏览器)detectIncognito.ts 演示:https://detectincognito.com/

不确定在v105中这对您是否有效。在“版本106.0.5249.103(官方构建)(x86_64)”中,隐身标签显示为“这是普通标签”。通过配额检测隐身模式已在2020年得到修复:https://bugs.chromium.org/p/chromium/issues/detail?id=1017120 - JDB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接