summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/resource/tests/browser_resources_css_messages.js
blob: 1b4b56cd4fcce632f59e699d00b532a70a8242a9 (plain)
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

// Test the ResourceCommand API around CSS_MESSAGE
// Reproduces the CSS message assertions from devtools/shared/webconsole/test/chrome/test_page_errors.html

const { MESSAGE_CATEGORY } = require("resource://devtools/shared/constants.js");

// Create a simple server so we have a nice sourceName in the resources packets.
const httpServer = createTestHTTPServer();
httpServer.registerPathHandler(`/test_css_messages.html`, (req, res) => {
  res.setStatusLine(req.httpVersion, 200, "OK");
  res.write(`<meta charset=utf8>
    <style>
      html {
        body {
          color: bloup;
        }
      }
    </style>Test CSS Messages`);
});

const TEST_URI = `http://localhost:${httpServer.identity.primaryPort}/test_css_messages.html`;

add_task(async function () {
  await testWatchingCssMessages();
  await testWatchingCachedCssMessages();
});

async function testWatchingCssMessages() {
  // Disable the preloaded process as it creates processes intermittently
  // which forces the emission of RDP requests we aren't correctly waiting for.
  await pushPref("dom.ipc.processPrelaunch.enabled", false);

  // Open a test tab
  const tab = await addTab(TEST_URI);

  const { client, resourceCommand, targetCommand } = await initResourceCommand(
    tab
  );

  const receivedMessages = [];
  const { onAvailable, onAllMessagesReceived } = setupOnAvailableFunction(
    targetCommand,
    receivedMessages,
    false
  );
  await resourceCommand.watchResources([resourceCommand.TYPES.CSS_MESSAGE], {
    onAvailable,
  });

  info(
    "Now log CSS warning *after* the call to ResourceCommand.watchResources and after " +
      "having received the existing message"
  );
  // We need to wait for the first CSS Warning as it is not a cached message; when we
  // start watching, the `cssErrorReportingEnabled` is checked on the target docShell, and
  // if it is false, we re-parse the stylesheets to get the messages.
  await BrowserTestUtils.waitForCondition(() => receivedMessages.length === 1);

  info("Trigger a CSS Warning");
  triggerCSSWarning(tab);

  info("Waiting for all expected CSS messages to be received");
  await onAllMessagesReceived;
  ok(true, "All the expected CSS messages were received");

  Services.console.reset();
  targetCommand.destroy();
  await client.close();
}

async function testWatchingCachedCssMessages() {
  // Disable the preloaded process as it creates processes intermittently
  // which forces the emission of RDP requests we aren't correctly waiting for.
  await pushPref("dom.ipc.processPrelaunch.enabled", false);

  // Open a test tab
  const tab = await addTab(TEST_URI);

  // By default, the CSS Parser does not emit warnings at all, for performance matter.
  // Since we actually want the Parser to emit those messages _before_ we start listening
  // for CSS messages, we need to set the cssErrorReportingEnabled flag on the docShell.
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    content.docShell.cssErrorReportingEnabled = true;
  });

  // Setting the docShell flag only indicates to the Parser that from now on, it should
  // emit warnings. But it does not automatically emit warnings for the existing CSS
  // errors in the stylesheets. So here we reload the tab, which will make the Parser
  // parse the stylesheets again, this time emitting warnings.
  await reloadBrowser();
  // and trigger more CSS warnings
  await triggerCSSWarning(tab);

  // At this point, all messages should be in the ConsoleService cache, and we can begin
  // to watch and check that we do retrieve those messages.
  const { client, resourceCommand, targetCommand } = await initResourceCommand(
    tab
  );

  const receivedMessages = [];
  const { onAvailable } = setupOnAvailableFunction(
    targetCommand,
    receivedMessages,
    true
  );
  await resourceCommand.watchResources([resourceCommand.TYPES.CSS_MESSAGE], {
    onAvailable,
  });
  is(receivedMessages.length, 3, "Cached messages were retrieved as expected");

  Services.console.reset();
  targetCommand.destroy();
  await client.close();
}

function setupOnAvailableFunction(
  targetCommand,
  receivedMessages,
  isAlreadyExistingResource
) {
  // timeStamp are the result of a number in microsecond divided by 1000.
  // so we can't expect a precise number of decimals, or even if there would
  // be decimals at all.
  const FRACTIONAL_NUMBER_REGEX = /^\d+(\.\d{1,3})?$/;

  // The expected messages are the CSS warnings:
  // - one for the rule in the style element
  // - two for the JS modified style we're doing in the test.
  const expectedMessages = [
    {
      pageError: {
        errorMessage: /Expected color but found ‘bloup’/,
        sourceName: /test_css_messages/,
        category: MESSAGE_CATEGORY.CSS_PARSER,
        timeStamp: FRACTIONAL_NUMBER_REGEX,
        error: false,
        warning: true,
      },
      cssSelectors: ":is(html) body",
      isAlreadyExistingResource,
    },
    {
      pageError: {
        errorMessage: /Error in parsing value for ‘width’/,
        sourceName: /test_css_messages/,
        category: MESSAGE_CATEGORY.CSS_PARSER,
        timeStamp: FRACTIONAL_NUMBER_REGEX,
        error: false,
        warning: true,
      },
      isAlreadyExistingResource,
    },
    {
      pageError: {
        errorMessage: /Error in parsing value for ‘height’/,
        sourceName: /test_css_messages/,
        category: MESSAGE_CATEGORY.CSS_PARSER,
        timeStamp: FRACTIONAL_NUMBER_REGEX,
        error: false,
        warning: true,
      },
      isAlreadyExistingResource,
    },
  ];

  let done;
  const onAllMessagesReceived = new Promise(resolve => (done = resolve));
  const onAvailable = resources => {
    for (const resource of resources) {
      const { pageError } = resource;

      is(
        resource.targetFront,
        targetCommand.targetFront,
        "The targetFront property is the expected one"
      );

      if (!pageError.sourceName.includes("test_css_messages")) {
        info(`Ignore error from unknown source: "${pageError.sourceName}"`);
        continue;
      }

      const index = receivedMessages.length;
      receivedMessages.push(resource);

      info(
        `checking received css message #${index}: ${pageError.errorMessage}`
      );
      ok(pageError, "The resource has a pageError attribute");
      checkObject(resource, expectedMessages[index]);

      if (receivedMessages.length == expectedMessages.length) {
        done();
      }
    }
  };
  return { onAvailable, onAllMessagesReceived };
}

/**
 * Sets invalid values for width and height on the document's body style attribute.
 */
function triggerCSSWarning(tab) {
  return ContentTask.spawn(tab.linkedBrowser, null, function frameScript() {
    content.document.body.style.width = "red";
    content.document.body.style.height = "blue";
  });
}