web-dev-qa-db-ja.com

.cssRulesを使用したクロスドメインスタイルシートへのアクセス

外部ドメインでホストされているいくつかのCSSファイルにアクセスしようとすると、Firebugで次のエラーが発生します。

Security error" code: "1000
rules = styleSheets[i].cssRules;

私が使用しているコードは次のとおりです。

$(document).ready(function () {
    $("p").live('mousedown', function getCSSRules(element) {
        element = $(this);
        var styleSheets = document.styleSheets;
        var matchedRules = [],
            rules, rule;
        for (var i = 0; i < styleSheets.length; i++) {
            rules = styleSheets[i].cssRules;
            for (var j = 0; j < rules.length; j++) {
                rule = rules[j];
                if (element.is(rule.selectorText)) {
                    matchedRules.Push(rule.selectorText);
                }
            }
        }
        alert(matchedRules);
    });
});

同じドメイン上のすべてのCSSファイルを移動する以外に、これを修正する方法はありますか?

24
Mircea

この問題の唯一の実際の解決策は、最初にCSSをCORSロードすることです。 CORS XMLHttpRequestを使用して外部ドメインからCSSをロードし、次のような方法でresponseText(この場合は実際にはresponseCSS)をページに挿入します。

function loadCSSCors(stylesheet_uri) {
  var _xhr = global.XMLHttpRequest;
  var has_cred = false;
  try {has_cred = _xhr && ('withCredentials' in (new _xhr()));} catch(e) {}
  if (!has_cred) {
    console.error('CORS not supported');
    return;
  }
  var xhr = new _xhr();
  xhr.open('GET', stylesheet_uri);
  xhr.onload = function() {
    xhr.onload = xhr.onerror = null;
    if (xhr.status < 200 || xhr.status >= 300) {
      console.error('style failed to load: ' + stylesheet_uri);
    } else {
      var style_tag = document.createElement('style');
      style_tag.appendChild(document.createTextNode(xhr.responseText));
      document.head.appendChild(style_tag);
    }
  };
  xhr.onerror = function() {
      xhr.onload = xhr.onerror = null;
      console.error('XHR CORS CSS fail:' + styleURI);
  };
  xhr.send();
}

このようにして、CSSファイルはメインページの応答と同じOriginドメインからのものとしてブラウザによって解釈され、スタイルシートのcssRulesプロパティにアクセスできるようになります。

14

2013年の時点で、<link>-要素に「crossorigin」属性を設定して、このCSSが信頼されていることをブラウザーに通知できます( MozillaW )。これが機能するためには、CSSをホストしているサーバーがAccess-Control-Allow-Origin: *ヘッダーを設定する必要があります。

その後、Javascriptを介してそのルールにアクセスできます。

10
Johannes Jander

FFを含むクロスブラウザの読み込みの問題を解決する小さな関数を書きました。 GitHubのコメントは、使用法の説明に役立ちます。 https://github.com/srolfe26/getXDomainCSS の完全なコード。

免責事項:以下のコードはjQueryに依存しています。

場合によっては、CORS設定を制御できない場所からCSSをプルしている場合、<link>タグが付いたCSSを取得するまで、解決すべき主な問題は、必要なCSSがいつ必要かを知ることになることがあります。ロードされ、使用する準備ができました。古いIEでは、CSSのロード時にon_loadリスナーを実行できました。

新しいブラウザでは、ファイルがいつ読み込まれるかを判断するために昔ながらのポーリングが必要なようであり、読み込みがいつ満たされるかを判断する際にブラウザ間の問題がいくつかあります。これらの癖のいくつかをキャッチするには、以下のコードを参照してください。

/**
 * Retrieves CSS files from a cross-domain source via javascript. Provides a jQuery implemented
 * promise object that can be used for callbacks for when the CSS is actually completely loaded.
 * The 'onload' function works for IE, while the 'style/cssRules' version works everywhere else
 * and accounts for differences per-browser.
 *
 * @param   {String}    url     The url/uri for the CSS file to request
 * 
 * @returns {Object}    A jQuery Deferred object that can be used for 
 */
function getXDomainCSS(url) {
    var link,
        style,
        interval,
        timeout = 60000,                        // 1 minute seems like a good timeout
        counter = 0,                            // Used to compare try time against timeout
        step = 30,                              // Amount of wait time on each load check
        docStyles = document.styleSheets        // local reference
        ssCount = docStyles.length,             // Initial stylesheet count
        promise = $.Deferred();

    // IE 8 & 9 it is best to use 'onload'. style[0].sheet.cssRules has problems.
    if (navigator.appVersion.indexOf("MSIE") != -1) {
        link = document.createElement('link');
        link.type = "text/css";
        link.rel = "stylesheet";
        link.href = url;

        link.onload = function () {
            promise.resolve();
        }

        document.getElementsByTagName('head')[0].appendChild(link);
    }

    // Support for FF, Chrome, Safari, and Opera
    else {
        style = $('<style>')
            .text('@import "' + url + '"')
            .attr({
                 // Adding this attribute allows the file to still be identified as an external
                 // resource in developer tools.
                 'data-uri': url
            })
            .appendTo('body');

        // This setInterval will detect when style rules for our stylesheet have loaded.
        interval = setInterval(function() {
            try {
                // This will fail in Firefox (and kick us to the catch statement) if there are no 
                // style rules.
                style[0].sheet.cssRules;

                // The above statement will succeed in Chrome even if the file isn't loaded yet
                // but Chrome won't increment the styleSheet length until the file is loaded.
                if(ssCount === docStyles.length) {
                    throw(url + ' not loaded yet');
                }
                else {
                    var loaded = false,
                        href,
                        n;

                    // If there are multiple files being loaded at once, we need to make sure that 
                    // the new file is this file
                    for (n = docStyles.length - 1; n >= 0; n--) {
                        href = docStyles[n].cssRules[0].href;

                        if (typeof href != 'undefined' && href === url) {
                            // If there is an HTTP error there is no way to consistently
                            // know it and handle it. The file is considered 'loaded', but
                            // the console should will the HTTP error.
                            loaded = true;
                            break;
                        }
                    }

                    if (loaded === false) {
                        throw(url + ' not loaded yet');
                    }
                }

                // If an error wasn't thrown by this point in execution, the stylesheet is loaded, proceed.
                promise.resolve();
                clearInterval(interval);
            } catch (e) {
                counter += step;

                if (counter > timeout) {
                    // Time out so that the interval doesn't run indefinitely.
                    clearInterval(interval);
                    promise.reject();
                }

            }
        }, step);   
    }

    return promise;
}
2
srolfe26

外部スタイルシートがホストされているドメインを制御できる場合は、 適切なAccess-Control-Allow-Originヘッダー を追加すると役立つ場合があります。

Access-Control-Allow-Origin: http://stylesheet-user.example.com
2
gerrit

CSSの一部が他の場所から来ている可能性があるが、興味のあるビットではないためにこれがトリガーされる場合は、次のようなtry ... catchブロックを使用してください。

function cssAttributeGet(selectorText,attribute) {
  var styleSheet, rules, i, ii;
  selectorText=selectorText.toLowerCase();
  if (!document.styleSheets) {
    return false;
  }
  for (i=0; i<document.styleSheets.length; i++) {
    try{
      styleSheet=document.styleSheets[i];
      rules = (styleSheet.cssRules ? styleSheet.cssRules : styleSheet.rules);
      for (ii=0; ii<rules.length; ii++) {
        if (
          rules[ii] && rules[ii].selectorText &&
          rules[ii].selectorText.toLowerCase()===selectorText &&
          rules[ii].style[attribute]
        ){
          return (rules[ii].style[attribute]);
        }
      }
    }
    catch(e){
      // Do nothing!
    };
  }
  return false;
}
1
Martin Francis

FirefoxとChromeでも同様の問題が発生しました。次のように、外部ドメインcssを含むcssファイルをドメインに追加することで、厳しい方法で問題を解決しました。

<style type="text/css">
@import url("https://externaldomain.com/includes/styles/cookie-btn.css");
</style>

速いけど汚い。ドメイン内のすべてのcssファイルを保持することをお勧めします。

1
radoslaw.busz

そのCSSファイルのプロキシを作成する必要がある場合があります。

0
Peter Örneholm