Мой внедренный ‹script› запускается после javascript целевой страницы, несмотря на использование run_at: document_start?

У меня возникают проблемы с порядком выполнения javascript, когда я использую сценарий содержимого для вставки некоторого javascript на страницу HTML:

Это моя HTML-страница, которую я использую для тестирования, test.html:

<html><head>    
<title>Test Page</title></head>
  <body>
    <script>
      console.log("In page");
    </script>
  </body>
</html>


Это javascript, который я использую для вставки дополнительного кода в HTML-страницу, injector.js:

var s = document.createElement("script");
s.src = chrome.extension.getURL("inject.js");
document.documentElement.appendChild(s);
console.log("Inject finished");


И это содержимое внедренного скрипта inject.js:

console.log("Inside inject.js");


И наконец, это мой manifest.json:

  "web_accessible_resources": [
    "inject.js"
  ],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["/injector.js"],
      "run_at": "document_start"
    }
  ]


Теперь, когда я открываю test.html, в моей консоли появляется следующее:

Inject finished
In page
Inside inject.js


У меня вопрос: почему In page распечатывается до Inside inject.js? Разве inject.js не должен запускаться первым, поскольку я установил run_at на document_start?

Если это сделано намеренно, могу ли я как-нибудь убедиться, что содержимое внутри inject.js выполняется перед сценарием внутри HTML-страницы , при этом ничего не меняя внутри HTML-страницы?

Установка атрибута async на false перед appendChild, похоже, не работает, поскольку он используется для упорядочивания между внешними скриптами, а не между внешними скриптами и внутристраничным скриптом.

Кстати, я тестирую это в Chrome 27.0.1453.9


person Hery    schedule 31.03.2013    source источник


Ответы (4)


Внедренные скрипты с атрибутами src выполняются в Chrome асинхронно.
Рассмотрим этот HTML-файл:

<html><head>
    <title>Chrome early script inject, Test Page</title>
    <script>
        var s = document.createElement ("script");
        s.src = "localInject1.js";
        document.documentElement.appendChild (s);
    </script>
</head>
<body>
<p>See the console.</p>
<script src="localInject2.js"></script>
<script> console.log("In page: ", new Date().getTime()); </script>
</body>
</html>

где localInject1.js просто:

console.log("In page's script 1: ", new Date().getTime());

и localInject2.js:

console.log("In page's script 2: ", new Date().getTime());


Номинально консоль должна показывать:

In page's script 1:   ...
In page's script 2:   ...
In page:   ...


Но часто, особенно при перезагрузках без кеширования, вы увидите:

In page's script 2:   ...
In page:   ...
In page's script 1:   ...


Настройка s.async = false; не имеет значения.


Я не уверен, ошибка это или нет. Firefox также выводит скрипты из строя, но кажется более последовательным в этом отношении. Кажется, нет любые соответствующие ошибки Chrome, и мне неясны спецификации.


Обойти:

Сценарии с кодом, установленным textContent, а не файлом, связанным с src, выполняются немедленно, как и следовало ожидать.
Например, если вы изменили injector.js на:

var s = document.createElement ("script");
s.src = chrome.extension.getURL ("inject.js");
document.documentElement.appendChild (s);

var s = document.createElement ('script');
s.textContent = 'console.log ("Text runs correctly!", new Date().getTime() );';
document.documentElement.appendChild (s);

console.log("Inject finished", new Date().getTime() );

вы бы увидели:

Text runs correctly! ...
Inject finished ...
In page
Inside inject.js


По общему признанию, это может быть проблемой в сценарии содержимого, но это может быть все, что вы можете сделать (в Chrome), пока эта проблема не будет решена W3C и поставщиками браузеров.

person Brock Adams    schedule 31.03.2013
comment
Я думаю, что единственная проблема обходного решения заключается в том, что код сложно писать в виде строки. Может помочь использование сценария для его создания. Я написал здесь. stackoverflow.com/questions/16713181/ - person Hui.Li; 29.05.2013
comment
На самом деле, @ Hui.Li, получить код в строку в javascript довольно просто. Просто оберните его в функцию EG: function foo() { /*Your code goes here*/ }, а затем используйте .toString() в функции. Например: s.textContent = '('+foo.toString ()+')();'. - person Brock Adams; 29.05.2013
comment
@BrockAdams Есть ли способ выполнить простейшую передачу сообщений перед выполнением этого скрипта в document_start? - person Srikanth Rayabhagi; 23.04.2014
comment
@SrikanthRayabhagi, вам нужно задать новый вопрос с подробностями о том, что вы пытаетесь сделать. - person Brock Adams; 23.04.2014

Теги JavaScript script не блокируют выполнение (к счастью!). Если вы хотите получить желаемую функциональность (ленивая загрузка), вам нужно будет сделать что-то вроде следующего:

function loadScript(scriptName, callback) {
   var sTag = document.createElement("script");
   sTag.src = scriptName;
   sTag.onreadystatechange = sTag.onload = function() {
    var state = s.readyState;

    if (!state || /loaded|complete/.test(state)) {
        callback();
    }
  };
 }

Это вызовет функцию обратного вызова при загрузке тега скрипта. Оттуда загрузите все и убедитесь, что каждый обратный вызов обработан, а затем запускайте свои события загрузки страницы в здравом уме, что у вас есть все библиотеки, которые вам нужны.

person Sébastien Renauld    schedule 31.03.2013

Когда вы вставляете javascript на такую ​​страницу, он появляется асинхронно. Когда у вас действительно есть тег <script> с самого начала, он будет синхронным и запускаться в правильном порядке.

Это вроде хлопот, но поскольку он асинхронный, ваш код будет продолжать выполняться, пока загружается другой файл сценария.

Чтобы узнать, когда сценарий завершен, вы должны обработать событие для сценария - проще всего использовать функцию jQuery getScript, которая предоставляет вам аргумент обратного вызова.

person Joe Enos    schedule 31.03.2013
comment
Привет, я полагаю, вы хотели использовать getScript внутри html-страницы, верно? Я добавил еще одно раздражение к своему вопросу: ... при этом ничего не меняя внутри html-страницы ?. Любая идея? - person Hery; 31.03.2013
comment
Не меняя HTML, я не понимаю, как - для того, чтобы сценарий запускался до того сценария, который там есть, вам нужно будет поместить тег сценария перед тем, который находится на странице. Но с этими расширениями можно делать много забавных вещей, так что, возможно, есть что-то, о чем я не слышал. - person Joe Enos; 31.03.2013

Существуют целые библиотеки, предназначенные для достижения того, что вы пытаетесь сделать, поскольку каждый браузер, кажется, добавляет свою долю ошибок. Взгляните на что-то вроде HeadJS или, если уже используете jQuery, что сказал Джо.

person Robert Hoffmann    schedule 31.03.2013
comment
Ни ответ Джо, ни HeadJS не применимы в контексте этой проблемы с расширением Chrome. OP не может контролировать источник целевой страницы (его SSCCE, образец HTML-кода предназначен только для краткой иллюстрации характера проблемы). - person Brock Adams; 01.04.2013