无法将API连接到Chrome扩展程序

时间:2014-01-05 12:08:25

标签: javascript jquery google-chrome google-chrome-extension

我正在开发chrome扩展程序。我想在点击popup.html中的按钮后将一些API连接到当前标签。我在popup.js

中使用此代码
$('button').click(function() {
    chrome.tabs.executeScript({
        file: 'js/ymaps.js'
    }, function() {});
});

ymaps.js中,我使用以下代码将API连接到当前标签:

var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
document.getElementsByTagName('head')[0].appendChild(script);

使用Yandex Maps需要此API。因此,在该代码之后,我创建<div>,其中应放置地图:

$('body').append('<div id="ymapsbox"></div>');

这个简单的代码只会将地图加载到创建的<div>

ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
    myMap = new ymaps.Map("ymapsbox", {
        center: [55.76, 37.64],
        zoom: 7
    });
}

我认为,一切都很清楚,如果你还在阅读,我会解释问题是什么。 当我点击popup.html中的按钮时,我会进入Chrome控制台Uncaught ReferenceError: ymaps is not defined。好像api库没有连接。但!当我手动输入控制台ymaps时 - 我获得了可用方法的列表,因此库已连接。那么为什么当我调用ymaps - 来自已执行的.js - 文件的对象时,我会收到这样的错误?

UPD:我还试图在ymaps.ready(init)函数中包含$(document).ready()

$(document).ready(function() {
    ymaps.ready(init);
})

但错误仍在出现。 下面的人说api库可能尚未加载。但是这段代码也会产生错误。

   setTimeout(function() {
        ymaps.ready(init);
    }, 1500);

我甚至试图这样做......

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
    if (changeInfo.status == "complete") {
        chrome.tabs.executeScript({
            file: 'js/gmm/yandexmaps.js'
        });
    }
});

3 个答案:

答案 0 :(得分:4)

ymaps未定义,因为您尝试在内容脚本中使用它,而库是在页面上下文中加载的(通过<script>标记)。

通常,您可以通过将库加载为内容脚本来解决问题,例如

chrome.tabs.executeScript({
    file: 'library.js'
}, function() {
    chrome.tabs.executeScript({
        file: 'yourscript.js'
    });
});

但是,这不会解决您的问题,因为您的库会在<script>标记中加载更多外部脚本。因此,只有网页中的脚本才能看到部分库(而不是内容脚本,因为单独的script execution environments)。

解决方案1:拦截<script>标记并将其作为内容脚本运行。

https://github.com/Rob--W/chrome-api/tree/master/scriptTagContext获取scriptTagContext.js,然后在其他内容脚本之前加载它。此模块通过将<script>(在内容脚本中创建)的执行环境更改为内容脚本来解决您的问题。

chrome.tabs.executeScript({
    file: 'scriptTagContext.js'
}, function() {
    chrome.tabs.executeScript({
        file: 'js/ymaps.js'
    });
});

有关文档,请参阅Rob--W/chrome-api/scriptTagContext/README.md 有关解决方案背后概念的解释,请参阅first revision of this answer

解决方案2:在页面上下文中运行

如果您 - 某些方式 - 不想使用以前的解决方案,那么还有另一个选项可以让代码运行。我强烈建议反对这种方法,因为它可能(并且会)在其他页面中引起冲突。然而,为了完整性:

通过页面中的<script>标记插入内容脚本(或者至少是使用外部库的扩展部分),在页面上下文中运行所有代码。这仅在您不使用任何Chrome扩展程序API时才有效,因为您的脚本将有效地使用网页的有限权限运行。

例如,您问题中的代码将按如下方式重构:

var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
script.onload = function() {
    var script = document.createElement('script');
    script.textContent = '(' + function() {
        // Runs in the context of your page
        ymaps.ready(init);//Waits DOM loaded and run function
        var myMap;
        function init() {
            myMap = new ymaps.Map("ymapsbox", {
                center: [55.76, 37.64],
                zoom: 7
            });
        }
    } + ')()';
    document.head.appendChild(script);
};
document.head.appendChild(script);

这只是将脚本的执行上下文切换到页面的众多方法之一。阅读Building a Chrome Extension - Inject code in a page using a Content script以了解有关其他可能选项的更多信息。

答案 1 :(得分:2)

这不是时间问题,而是与“执行环境”相关的问题。

将脚本注入Web页面的JS上下文(将脚本标记插入head),但尝试从内容脚本的JS上下文中调用ymaps。然而,内容脚本在一个孤立的世界中“活着”,无法访问网页的JS上下文(请看 the docs )。

编辑(感谢Rob的评论)

通常,您可以捆绑该库的副本并将其作为内容脚本注入。在您的特定情况下,这也无济于事,因为库本身会将脚本标记插入到加载依赖项中。


可能的解决方案:

根据您的具体要求,您可以:

  1. 您可以在弹出窗口或新标签中显示(并让用户与之交互),而不是将地图插入网页。您将在包含该库的新窗口/选项卡中提供要加载的HTML文件(引用该文件的捆绑副本或在 relaxing the default Content Security Policy 之后使用CDN - 前者是推荐的方式)。

  2. 修改外部库(即删除脚本标记的插入)。我建议反对它,因为这种方法引入了额外的维护“成本”(例如,每次更新库时都需要重复该过程)。

  3. 将所有代码注入网页的上下文中 可能的陷阱:弄乱网页JS,例如覆盖已定义的变量/函数。 此外,如果您需要与chrome。* API(Web页面的JS上下文无法使用,因此您需要设置专有的消息传递机制,例如使用自定义事件),此方法将变得越来越复杂。

  4. 然而,如果你只需要执行一些简单的初始化代码,这是一个可行的替代方案:

    E.g:

    <强> ymaps.js:

    function initMap() {
        ymaps.ready(init);//Waits DOM loaded and run function
        var myMap;
        function init() {
            myMap = new ymaps.Map("ymapsbox", {
                center: [55.76, 37.64],
                zoom: 7
            });
        }
    }
    
    $('body').append('<div id="ymapsbox"></div>');
    var script1 = document.createElement('script');
    script1.src = 'http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU';
    script1.addEventListener('load', function() {
        var script2 = document.createElement('script');
        var script2.textContent = '(' + initMap + ')()';
        document.head.appendChild(script2);
    });
    document.head.appendChild(script1);
    
    罗已经指出了这个主题的重要资源:
    Building a Chrome Extension - Inject code in a page using a Content script

答案 2 :(得分:-1)

Yandex本身有一个更简单的解决方案。

// div-container of the map
<div id="YMapsID" style="width: 450px; height: 350px;"></div>

<script type="text/javascript">
    var myMap;
    function init (ymaps) {
        myMap = new ymaps.Map("YMapsID", {
            center: [55.87, 37.66],
            zoom: 10
        });
        ...
    }
</script>

// Just after API is loaded the function init will be invoked
// On that moment the container will be ready for usage
<script src="https://...?load=package.full&lang=ru_RU&onload=init">

<强>更新

要正确使用此功能,您必须确保init已准备好加载Yandex-scirpt。这可以通过以下方式实现。

  1. 您将init放在html页面上。

  2. 您可以从放置init的相同脚本开始加载Yandex脚本。

  3. 您在html页面上创建一个调度程序,用于捕获两个组件中的ready个事件。

  4. 您还需要检查您的容器是否已创建到Yandex脚本加载的那一刻。

    更新2

    有时发生init脚本的加载晚于Yandex-lib。在这种情况下,值得检查:

    if(typeof ymaps !== 'undefined' && typeof ymaps.Map !== 'undefined') {
        initMap();
    }
    

    当我相对于容器移动地图画布时,我遇到了定位地图画布的问题。例如,当容器处于衰落模态窗口中时,这可能发生。在这种情况下,最好是调用窗口大小调整事件:

    $('#modal').on('shown.bs.modal', function (e) {
        window.dispatchEvent(new Event('resize'));
    });