Клиентское динамическое удаление <script> Теги в <head>

Можно ли удалить теги script в <head> клиентского документа HTML-документа и до выполнения этих тегов?

На стороне сервера я могу вставить <script> выше всех других тегов <script> в <head>, кроме одного, и я хотел бы удалить все последующие скрипты. У меня нет возможности удалять теги <script> со стороны сервера.

Что я пробовал:

(function (c,h) {
  var i, s = h.getElementsByTagName('script');
  c.log("Num scripts: " + s.length);
  i = s.length - 1;
  while(i > 1) {
    h.removeChild(s[i]);
    i -= 1;
  }
})(console, document.head);

Однако зарегистрированное количество скриптов выходит только на 1, так как (как указывал @ryan) код выполняется до того, как DOM будет готов. Хотя обертка кода выше в обратном вызове события document.ready позволяет правильно подсчитать количество тегов <script> в <head>, ожидая, пока DOM не будет готов, чтобы предотвратить загрузку скриптов.

Есть ли надежное средство манипулирования HTML до готовности DOM?

Фон

Если вам нужен больше контекста, это часть попытки консолидации скриптов, где нет возможности для агрегации на стороне сервера. Многие загружаемые библиотеки JS находятся на CMS с ограниченными параметрами конфигурации. Содержимое в основном статично, поэтому очень мало внимания уделяется ручному агрегированию JavaScript и его обслуживанию из другого места. Любые предложения по альтернативным методам агрегирования также приветствуются.

+20
05 окт. '12 в 14:07
источник поделиться
5 ответов

Поскольку вы не можете помешать будущим тегам <script> оценивать (всякий раз, когда тэг </script> найден, соответствующий код <script> извлекается и оценивается. <script src> блокирует загрузку документа дальше до источника выбирается, если не установлен атрибут async), необходимо использовать другой подход.
Прежде чем представить решение, я спрашиваю: что может помешать script в теге <script> выполнить? Действительно,

  • Удаление <script> из исходного кода.
  • Добавление директивы Content Security для блокировки скриптов из определенных источников.
  • Запуск ошибки (runtime).

1 очевидно, и 2 можно получить из документации, поэтому я остановлюсь на 3. Приведенные ниже примеры очевидны и должны быть скорректированы для реальных случаев использования.

проксирование

Вот общий шаблон для проксирования существующих методов:

(function(Math) {
   var original_method = Math.random;
   Math.random = function() {
       // use arguments.callee to read source code of caller function
       if (/somepattern/.test(arguments.callee.caller)) {
           Math.random = original_method; // Restore (run once)
           throw 'Prevented execution!';
       }
       return random.apply(this, arguments); // Generic method proxy
   };
})(Math);
// Demo:
function ok()    { return Math.random(); }
function notok() { var somepattern; return Math.random(); }

В этом примере блок-блокиратор работает только один раз. Вы можете удалить строку восстановления или добавить var counter=0; и if(++counter > 1337), чтобы восстановить метод после 1337 вызовов.

arguments.callee.caller null, если вызывающий объект не является функцией (например, код верхнего уровня). Не катастрофа, вы можете прочитать из аргументов или ключевое слово this или любую другую переменную среды, чтобы определить, должно ли выполнение должно быть остановлено.
Демо: http://jsfiddle.net/qFnMX/

Отклонить сеттеры/геттеры

Здесь общий шаблон для разбивающих устройств:

Object.defineProperty(window, 'undefinable', {set:function(){}});
/*fail*/ function undefinable() {} // or window.undefinable = function(){};

Демо: http://jsfiddle.net/qFnMX/2/

И получатели, конечно:

(function() {
    var actualValue;
    Object.defineProperty(window, 'unreadable', {
        set: function(value) {
            // Allow all setters for example
            actualValue = value;
        },
        get: function() {
            if (/somepattern/.test(arguments.callee.caller)) {
                // Restore, by deleting the property, then assigning value:
                delete window.unreadable;
                window.unreadable = actualValue;
                throw 'Prevented execution!';
            }
            return actualValue;
        },
        configurable: true // Allow re-definition of property descriptor
    });
})();
function notok() {var somepattern = window.unreadable; }
// Now OK, because 
function nowok() {var somepattern = window.unreadable; }
function ok()    {return unreadable;}

Демо: http://jsfiddle.net/qFnMX/4/

И так далее. Посмотрите исходный код сценариев, которые вы хотите заблокировать, и вы сможете создать script -специфический (или даже общий) script -кратный шаблон.

Единственным недостатком метода запуска ошибки является то, что ошибка регистрируется в консоли. Для обычных пользователей это не должно быть проблемой.

+9
10 окт. '12 в 16:50
источник

Правильно, была другая немного сумасшедшая идея, чем моя первая, но это зависит от того, какой контроль у вас есть, чтобы вставлять теги в начало страниц:

требование

Проще говоря, если вы можете вставить тег <noscript>, как показано ниже, перед любым объявлением <script> в голове, и затем вы можете добавить тег </noscript> в конец головы, а также окончательный script фрагмент - вы должны будете делать все, что хотите, с разметкой между тегами noscript, прежде чем оно будет записано обратно на страницу.

Хорошая вещь об этом подходе заключается в том, что агенты script -disabled будут просто игнорировать и анализировать разметку, но агенты script -enabled будут хранить содержимое, но не использовать его... точно, что нужно.

реализация

В то время как это предназначено для использования с головой, его можно легко использовать таким же образом в теле, хотя это должно быть отдельной реализацией. Это связано с тем, что он должен работать со сбалансированным и полным деревом node из-за характера тегов (если только вам не удастся обернуть всю разметку в noscript?!?).

расквитаться /Downsides

Это не полностью доказательство, потому что скрипты могут лежать вне тегов головы и тела - по крайней мере, до того, как они разбираются - но, похоже, он работает уверенно на всем, что я тестировал до сих пор... и это не так, t полагаться на небольшое количество случайного ajax-приведенного в действие кода, который сломается при первом знаке обновления браузера;)

Плюс мне также нравится идея тегов script в тегах noscript...

<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<noscript id="__disabled__">
  <script src="jquery.js"></script>
  <title>Another example</title>
  <script>alert(1);</script>
  <link rel="stylesheet" type="text/css" href="core.css" />
  <style>body { background: #ddd; }</style>
</noscript>
<script>
(function(){
  var noscript = document.getElementById('__disabled__');
  if ( noscript ) {
    document.write(
      String(noscript.innerHTML)
        /// IE entity encodes noscript content, so reverse
        .replace(/&gt;/gi,'>')
        .replace(/&lt;/gi,'<')
        /// simple disable script regexp
        .replace(/<script[^>]*>/gi,'<'+'!--')
        .replace(/<\/script>/gi,'//--'+'>')
    );
  }
})()
</script>
</head>
+8
13 окт. '12 в 10:09
источник

Вы можете попытаться использовать события DOM Mutation:

DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified

так:

document.head.addEventListener ('DOMNodeInserted', function(ev) {
   if (ev.target.tagName == 'SCRIPT') {
       ev.target.parentNode.removeChild(ev.target);
   }
}, false);

Также вы можете попробовать новый способ сделать это через MutationObserver

+7
08 окт. '12 в 19:58
источник

Хорошо, поэтому мне еще нужно проверить это в Internet Explorer (я сомневаюсь, что это сработает), и не ругайте меня за ужас хаков... Я знаю;), но, похоже, это работает в FireFox, Safari, Chrome и Opera на Mac OSX - последние публичные выпуски этих пользовательских атак, по крайней мере. Я посмотрю, смогу ли я улучшить его, когда получаю доступ к машине Windows... хотя я не очень надеюсь на IE.

(function(xhr,d,de){
  d = document;
  try{
    de = ((de = d.getElementsByTagName('html')[0]) 
      ? de : ( d.documentElement ? d.documentElement : d.body ));
    /// this forces firefox to reasses it dom
    d.write('&nbsp;');
    /// make an ajax request to get the source of this page as a string
    /// this could be improved, I've just chucked it in as an example
    if (window.XMLHttpRequest) {
      xhr = new window.XMLHttpRequest;
    }else{
      xhr = new ActiveXObject("MSXML2.XMLHTTP");
    }
    if ( xhr ) {
      /// open non-async so the browser has to wait
      xhr.open('GET', window.location, false);
      xhr.onreadystatechange = function (e,o,ns){
        /// when we've got the source of the page... then
        if ((o = e.target) && (o.readyState == 4) && (o.status == 200)) {
          /// remove the script tags
          window.ns = ns = String(o.responseText)
              .replace(/<script[^>]*>/gi,'<'+'!--')
              .replace(/<\/script>/gi,'//--'+'>');
          /// fix for firefox - this causes a complete 
          /// rewrite of the main docelm
          if ( 'MozBoxSizing' in de.style ) {
            de.innerHTML = ns;
          }
          /// fix for webkit, this seems to work, whereas 
          /// normal document.write() doesn't. Probably 
          /// because the window.location resets the document.
          else {
            window.location = 'javascript:document.write(window.ns);';
          }
        }
      };
      xhr.send({});
    }
  }
  catch(ex){}
})();

Просто сказать, что я тестировал это почти с каждым типом тега script, о котором я могу думать, где бы я ни разместил их. И я еще не успел прорваться. Как я уже сказал, забавный вопрос... хотя я не знаю, насколько хорошо это работает в производственной среде: S;)

В основном это должно быть помещено как тег script в верхней части заголовка.

Пример теста:

http://pebbl.co.uk/stackoverflow/12748067.html

+6
08 окт. '12 в 21:19
источник

Нет, вы не можете

Я не могу найти официальную документацию прямо сейчас, но, поскольку я читаю Высокопроизводительный Javascript из Nicholas Zakas, когда движок рендеринга обнаружил тег script, он останавливает рендеринг HTML (так что никакой другой node не создается), загружает script и выполняет его. Затем он продолжает показывать HTML. Поэтому, когда вы выполняете "document.write()" в теге, результат добавляется JUST после тега, затем отображается остальная часть страницы.

(Я не знаю, могу ли я вставить здесь абзац книги...)

Таким образом, это не похоже на рендеринг страницы, тогда вы удалите node и script, которые не будут выполнены, когда браузер обнаружит тег, который вы ничего не можете сделать до тех пор, пока этот код не будет выполнен.

У нас была очень похожая проблема в нашем продукте, мы добавили тег script в DOM, и нам понадобился какой-то код, который должен быть выполнен JUST до начала нового запуска тега, после недели исследования нам пришлось найти другое решение.

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

+1
08 окт. '12 в 20:14
источник

Посмотрите другие вопросы по меткам или Задайте вопрос