检测代码是否作为Chrome扩展运行

22

我正在处理一些需要作为页面运行的代码,如果它作为Chrome扩展运行,我想能够做更多的事情。我使用的是:

<script>
if (chrome && chrome.extension) {
    // extension stuff
}
</script>

这似乎是一个很好的capability detection。使用用户代理字符串会给我带来麻烦,因为无论上下文是Web页面还是扩展程序,它都是相同的。

问题:是否有其他更可靠的技术来检测代码是否在Chrome扩展程序中运行?

更新:我想知道是否有一些内容可以放入我的manifest.json文件中,然后再读回来。请注意,我正在开发的扩展程序不是持久运行的东西,它是一个内容应用程序,在单个窗口或浏览器选项卡中运行,并且不需要与其他窗口、选项卡或其他任何内容交互。


1
你可以尝试获取国际化字符串,尽管检查扩展状态并不是它的预期目的:http://code.google.com/chrome/extensions/i18n.html - Digital Plane
8个回答

42

这里的答案很复杂,而你可以通过检查 chrome.runtime.id 的存在和非空来轻松地检测你是否在 Chrome 扩展中运行:

if (window.chrome && chrome.runtime && chrome.runtime.id) {
    // Code running in a Chrome extension (content script, background page, etc.)
}

1
非常好的观点。chrome.runtime是作为Chrome 22的一部分发布的(https://developer.chrome.com/extensions/runtime#property-id),该版本于2012年9月25日发布(http://googlechromereleases.blogspot.com/2012/09/stable-channel-update_25.html)...比问题和被接受的答案发布晚了一年。我正在更新被接受的答案。 感谢您的挖掘! - artlung
在这两种情况下,条件都为真。无论是从扩展弹出窗口内部检查还是在浏览器页面视图中检查。 - Atif Hussain
它是否比使用 chrome && chrome.extension 有优势? - OfirD
@HeyJude 是的,没错!chrome 只在基于 Chromium 的浏览器中公开可用。如果你使用 chrome &&,当脚本在非 Chrome 浏览器(如 Firefox 和 Safari)的常规网页中运行时,你会得到一个运行时错误。 - Rob W
哦,当然,你是对的。但除此之外,使用.runtime是否比使用.extension更有优势? - OfirD
这太棒了!现在我不必切换代码来测试Angular 10 Chrome扩展(popup.html脚本部分)作为浏览器扩展,也可以在常规浏览器窗口中使用ng serve!在普通浏览器窗口中自动重新加载,更方便开发浏览器扩展UI。 - RcoderNY

8

我需要类似的东西。

但是我不需要关心试图欺骗代码的网站。

const SCRIPT_TYPE = (() => {
    if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() === window) {
        return 'BACKGROUND';
    } else if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() !== window) {
        return 'POPUP';
    } else if (!chrome || !chrome.runtime || !chrome.runtime.onMessage) {
        return 'WEB';
    } else {
        return 'CONTENT';
    }
})();

上述内容应该检测以下4种情况:
  • JavaScript在后台页面中运行
  • JavaScript在弹出页面/iframe中运行
  • JavaScript在上下文脚本中运行
  • JavaScript直接在网站上运行

5

实际上,这是一个不错的方法。从理论上讲(不确定是否相关,例如可能会提供漏洞),它很容易被欺骗。我想这取决于您的上下文环境,这个问题有多么重要。

这里有一个稍微更强的想法:

if (chrome &&
    chrome.windows &&
    chrome.windows.get &&
    typeof chrome.windows.get === 'function' &&
    chrome.windows.get.toString() === 'function get() { [native code] }')

这个想法和你的一样,尽管稍微强一些,因为据我所知,使一个对象成为函数并让它的 toString() 值具有该值是不可能的,因为它不是有效的语法,因此即使尝试欺骗该值,除非修改本机代码(这需要不同级别的黑客),否则不起作用。
我不确定像这样检查东西是否需要权限,但我希望思路是清楚的。
更新
我刚意识到“本机代码”语法的想法可以被欺骗,方法是给现有函数取一个别名。例如:
var FakeFn = Object.create;
FakeFn.toString(); // "function create() { [native code] }"

但这可以通过仔细选择我们使用的函数来解决,因为名称出现在字符串中。 get 可能太常见了,但如果我们选择一个不常用的函数名称(例如 chrome.tabs.captureVisibleTab 的 captureVisibleTab),它仅在 Chrome 扩展中实现,那么它仍然是一个非常可移植的解决方案,因为与基本检查不同,代码可以被其他本地用户代码欺骗,预先知道浏览器没有实现任何具有此名称的本机函数,因此在所有浏览器和所有用户代码中仍然安全。
更新
正如 @Mathew 指出的那样,这个想法是可以被愚弄的(尽管似乎只会恶意)。我以为我可以通过比较 Function.prototype.toString 来修补这个问题,但发现即使是那样也可以通过给原始 toString 方法取别名并创建一个新的方法,在某些函数中返回错误的字符串,在其他函数中返回原始字符串,来欺骗它。
总之,我的想法比原来的想法稍微强一些,因为它几乎排除了所有意外碰撞的可能性(比 OP 的想法稍微多一点),但肯定不能防御恶意攻击,就像我最初认为的那样。

有些有趣,而且效果符合预期,但我不确定它比我的更具可移植性。我在想是否有什么东西可以放入我的manifest.json文件中,然后以某种方式将其读回来。感谢您的参与。 - artlung
1
@artlung,任何这样的方法都比我的不太可移植,让我解释一下:整个扩展API(据我所知,我对API并不是很精通)都是基于JavaScript的。因此,所有调用都将符合JS标准,并采用常规的obj.ref.fn()形式,根据定义可以被欺骗。我的方法更具可移植性的原因是它无法被欺骗。除非您篡改浏览器本身,否则无法创建通过这些条件的对象,而我可以在任何浏览器中轻松创建window.chrome={extension:true},您的代码将认为它是Chrome扩展程序。 - davin
你提出了一个很好的观点。我认为我的意图并不是要严格执行非欺骗性,而只是区分Chrome(和许多变体)的简单使用和作为扩展程序加载的编码。非常感谢您认真思考这个问题! - artlung
这样做可能有效,但仍不能保证。有人可以这样做:chrome = { windows: { get: function() {} } }; chrome.windows.get.toString = function() { return 'function get() { [native code] }' };。当然,他们基本上必须是故意这样做的。 - Matthew Crumley

1

感谢Chad Scira提供的原始答案,我的翻译基于此。


我采用了ES2021标准压缩了Chad的答案。也删除了以前版本所需的许多重复内容。

const runningAt = (() => {
    let getBackgroundPage = chrome?.extension?.getBackgroundPage;
    if (getBackgroundPage){
        return getBackgroundPage() === window ? 'BACKGROUND' : 'POPUP';
    }
    return chrome?.runtime?.onMessage ? 'CONTENT' : 'WEB';
})();

以下内容应该检测到以下4种情况:
  • JavaScript在后台页面运行
  • JavaScript在弹出页面/iframe中运行
  • JavaScript在上下文脚本中运行
  • JavaScript直接在网站上运行

1

我知道这已经过时了,但我想提供另一种选择。您可以向Chrome扩展程序添加另一个JavaScript文件,以便它包含两个.js文件。manifest.json应该包括:

"js": ["additionalscript.js", "yourscript.js"]

additionalscript.js 可以简单地声明一个变量 var isextension = true。yourscript.js 可以检查 typeof isextension != 'undefined'
但也许更有趣的例子是,它可以声明
var adminpassword='letmein'

现在,当扩展程序运行时,yourscript.js 只能访问 adminpassword。

(当然,如果插件所在的计算机可能会受到攻击,您不会将密码嵌入脚本文件中)


我认为你的意思是“脚本”,而不是“js”。你应该始终将测试作为 typeof isextension != 'undefined' 进行,否则当 additionalscript.js 未被执行时,会出现 JavaScript 异常。你还应该注意,这是清单文件中的“后台”部分。 - Dan McGrath
感谢@DanMcGrath - 我在manifest.json文件中有一个“content_scripts”部分,其中包含一个“js”条目:“content_scripts”:[ { “matches”:[“*:// * .google.com / *”], “js”:[“One.js”,“Two.js”] } ]。并且检查未定义的好方法。 - Debby Mendez

1

Chrome没有提供任何直接的API来检查应用程序的运行状态,即它是在扩展弹出窗口中运行还是在Chrome页面视图中运行。然而,一个间接的技巧可以起作用,我们可以匹配扩展主体分辨率,该分辨率等于或小于CSS中指定的分辨率(在这种情况下,扩展弹出窗口将打开),或大于该分辨率(在这种情况下,网页视图将打开)。


1
我注意到在Chrome中,全局window对象的属性chrome对象无法被删除。如果它是用户定义的属性,则删除操作成功。因此,您可以通过以下方式进行测试:
var isRunningInExtension = (!(delete window.chrome) && chrome.extension) ? 
                           true : false;

更新: 上面的代码并不能真正保证代码在Chrome扩展中运行。任何人都可以创建一个名为“chrome”的对象,带有一个“extension”属性,然后冻结/封印该对象-这样就足以通过检查,并得到一个错误的结果,即你的代码正在Chrome扩展中运行。
确保你的代码在扩展中运行,你必须在运行任何JavaScript之前测试全局chrome对象-这样你就可以保证在测试之前不会创建任何假的chrome对象。
一种可能的解决方案是使用iframe-在我下面的示例中,我使用iframe的sandbox属性来指示iframe不执行任何脚本(甚至是使用script标记包含的脚本)-这样我就可以确保没有脚本能够修改全局window.chrome对象。
(function() {
  var isChromeExtension = (function () {
    var contentWindow,
        iframe = document.createElement("iframe"),
        isChromeExtension;
    // test for sandbox support. It is supported in most recent version of Chrome
    if ("sandbox" in iframe) {
      try {
        iframe.sandbox = "allow-same-origin";
        iframe.src=location.href;
        iframe.style="display: none";
        document.body.appendChild(iframe);
        contentWindow = iframe.contentWindow;
        isChromeExtension = !!(contentWindow.chrome && contentWindow.chrome.extension);
        document.body.removeChild(iframe);
      } catch(e) {}
    }
    return isChromeExtension;
  }());
}());

结果可能是:

  • true - 如果代码正在Chrome扩展中运行
  • false - 如果代码没有在Chrome扩展中运行
  • undefined - 如果浏览器不支持iframe的沙盒或测试期间发生了某些错误

我不确定你对delete的工作原理的理解是否如你所说。我也可以创建一个对象,比如window.g = {x:1},并在其上运行delete,当尝试删除它时,会得到类似的结果为false。...也许需要更完整的示例来说明这个问题。 - artlung
你的新方法性能不是很好,也不是十分可靠。如果环境真的恶意,那么任何对象,包括 document 都可能被伪造。没有办法防御这种情况,所以你应该选择最易读和直接的方法。 - Rob W
确实不太高效 - 它将整个页面/样式/脚本加载到iframe中,但是没有任何脚本会被执行(内联或使用script标签包含)- 这就是沙盒的作用 - 因此从客户端无法更改全局对象,甚至模拟文档对象。请记住,Chrome对象无法被删除,但每个人都可以覆盖它 - 所以我可以这样做:window.chrome = {runtime: {id: {}}}; 而你建议的答案会说我的代码正在扩展内运行,而事实并非如此。 - ttsvetkov

0

要检测Chrome应用和扩展程序,请使用:

chrome.app.getDetails()

代码运行时,在其他浏览器上会抛出异常,这与问题要求的不完全相符。 - DazChong

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