Blame view

lib/jsdom/living/helpers/stylesheets.js 4.11 KB
858f2bdf5   Boyan Georgiev   fixes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  "use strict";
  const cssom = require("cssom");
  const whatwgEncoding = require("whatwg-encoding");
  const whatwgURL = require("whatwg-url");
  
  // TODO: this should really implement https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
  // It (and the things it calls) is nowhere close right now.
  exports.fetchStylesheet = (elementImpl, urlString) => {
    const parsedURL = whatwgURL.parseURL(urlString);
    return fetchStylesheetInternal(elementImpl, urlString, parsedURL);
  };
  
  // https://drafts.csswg.org/cssom/#remove-a-css-style-sheet
  exports.removeStylesheet = (sheet, elementImpl) => {
    const { styleSheets } = elementImpl._ownerDocument;
    styleSheets.splice(styleSheets.indexOf(sheet, 1));
  
    // Remove the association explicitly; in the spec it's implicit so this step doesn't exist.
    elementImpl.sheet = null;
  
    // TODO: "Set the CSS style sheet’s parent CSS style sheet, owner node and owner CSS rule to null."
    // Probably when we have a real CSSOM implementation.
  };
  
  // https://drafts.csswg.org/cssom/#create-a-css-style-sheet kinda:
  // - Parsing failures are not handled gracefully like they should be
  // - The import rules stuff seems out of place, and probably should affect the load event...
  exports.createStylesheet = (sheetText, elementImpl, baseURL) => {
    let sheet;
    try {
      sheet = cssom.parse(sheetText);
    } catch (e) {
      if (elementImpl._ownerDocument._defaultView) {
        const error = new Error("Could not parse CSS stylesheet");
        error.detail = sheetText;
        error.type = "css parsing";
  
        elementImpl._ownerDocument._defaultView._virtualConsole.emit("jsdomError", error);
      }
      return;
    }
  
    scanForImportRules(elementImpl, sheet.cssRules, baseURL);
  
    addStylesheet(sheet, elementImpl);
  };
  
  // https://drafts.csswg.org/cssom/#add-a-css-style-sheet
  function addStylesheet(sheet, elementImpl) {
    elementImpl._ownerDocument.styleSheets.push(sheet);
  
    // Set the association explicitly; in the spec it's implicit.
    elementImpl.sheet = sheet;
  
    // TODO: title and disabled stuff
  }
  
  function fetchStylesheetInternal(elementImpl, urlString, parsedURL) {
    const document = elementImpl._ownerDocument;
    let defaultEncoding = document._encoding;
    const resourceLoader = document._resourceLoader;
  
    if (elementImpl.localName === "link" && elementImpl.hasAttributeNS(null, "charset")) {
      defaultEncoding = whatwgEncoding.labelToName(elementImpl.getAttributeNS(null, "charset"));
    }
  
    function onStylesheetLoad(data) {
      const css = whatwgEncoding.decode(data, defaultEncoding);
  
      // TODO: MIME type checking?
      if (elementImpl.sheet) {
        exports.removeStylesheet(elementImpl.sheet, elementImpl);
      }
      exports.createStylesheet(css, elementImpl, parsedURL);
    }
  
    resourceLoader.fetch(urlString, {
      element: elementImpl,
      onLoad: onStylesheetLoad
    });
  }
  
  // TODO this is actually really messed up and overwrites the sheet on elementImpl
  // Tracking in https://github.com/jsdom/jsdom/issues/2124
  function scanForImportRules(elementImpl, cssRules, baseURL) {
    if (!cssRules) {
      return;
    }
  
    for (let i = 0; i < cssRules.length; ++i) {
      if (cssRules[i].cssRules) {
        // @media rule: keep searching inside it.
        scanForImportRules(elementImpl, cssRules[i].cssRules, baseURL);
      } else if (cssRules[i].href) {
        // @import rule: fetch the resource and evaluate it.
        // See http://dev.w3.org/csswg/cssom/#css-import-rule
        //     If loading of the style sheet fails its cssRules list is simply
        //     empty. I.e. an @import rule always has an associated style sheet.
        const parsed = whatwgURL.parseURL(cssRules[i].href, { baseURL });
        if (parsed === null) {
          const window = elementImpl._ownerDocument._defaultView;
          if (window) {
            const error = new Error(`Could not parse CSS @import URL ${cssRules[i].href} relative to base URL ` +
                                    `"${whatwgURL.serializeURL(baseURL)}"`);
            error.type = "css @import URL parsing";
            window._virtualConsole.emit("jsdomError", error);
          }
        } else {
          fetchStylesheetInternal(elementImpl, whatwgURL.serializeURL(parsed), parsed);
        }
      }
    }
  }