Bug: needsUpdate targeting attribute throws while offline, breaking ASRouter initialization
Status: RESOLVED
Priority: P1
Resolution: FIXED
Created: 2023-05-30 05:50:47+00:00
Resolved: 2023-06-22 14:51:12+00:00

The `needsUpdate` attribute intentionally [throws](https://searchfox.org/mozilla-central/rev/0c2945ad4769e2d4428c72e6ddd78d60eb920394/browser/components/newtab/lib/ASRouterTargeting.jsm#257) if the update check fails.

I think this is fine for message targeting evaluation, but it causes a problem for [this method](https://searchfox.org/mozilla-central/rev/0c2945ad4769e2d4428c72e6ddd78d60eb920394/browser/components/newtab/lib/ASRouter.jsm#1170-1172), which basically resolves the whole targeting environment on every `setState` call if devtools are enabled. The exception causes the `Promise.all` in that method to reject as well. This method is only intended for use with the ASRouter devtools, but in order to keep the state rendered in the devtools up to date, we update it every time the internal ASRouter state changes. So when `ASRouter.setState` is called, it invokes this `getTargetingParameters` method, and never resolves.

And because ASRouter's own `init` method calls `setState` before loading providers, message groups, messages, etc., an unhandled exception in targeting attribute resolution can stop ASRouter from ever loading any messages. Most targeting attributes don't throw under any circumstances, but we don't do a lot of testing offline, naturally. In this case, being offline causes the update service's check to fail, which causes the attribute getter to throw.

I took another approach to resolving this in bug 1833913, to change `getTargetingParameters` so that it handles exceptions in targeting getters. This doesn't affect JEXL evaluation, just the evaluation that happens when we inject targeting environment state into ASRouter devtools.

I'm not aware of any other kinds of evaluators. But it still seems worth considering if we want targeting attributes to intentionally throw under any circumstances. Would it be preferable for it to just return `false` or `undefined` or some other default value? Aside from the risk of causing failures in ASRouter and related code, I don't think that changes anything.

In JEXL, these unhandled exceptions result in an `undefined` result. But you can see what happens in practice if you go offline and test this in console:
```js
var { QueryCache, ASRouterTargeting } = ChromeUtils.import(
  "resource://activity-stream/lib/ASRouterTargeting.jsm"
);
var { ASRouter } = ChromeUtils.import(
  "resource://activity-stream/lib/ASRouter.jsm"
);
QueryCache.expireAll();
var result1 = await ASRouter.evaluateExpression({
  expression: "!needsUpdate",
  context: ASRouterTargeting.Environment,
});
console.log('result1 :>> ', result1.evaluationStatus.result);
// result1 :>> undefined
var result2 = await ASRouter.evaluateExpression({
  expression: "!needsUpdate",
  context: ASRouterTargeting.Environment,
});
console.log('result2 :>> ', result2.evaluationStatus.result);
// result2 :>> true
```

Basically, the first time the targeting attribute is used, its value hasn't been cached, so it uses the update service, which fails. So it throws, and you can see the error logged in the console. You can also see what it's doing if you set breakpoints inside the targeting getter.

On the first call, it makes it [here](https://searchfox.org/mozilla-central/rev/0c2945ad4769e2d4428c72e6ddd78d60eb920394/browser/components/newtab/lib/ASRouterTargeting.jsm#256-258), throws, and ultimately the targeting expression evaluation result is `undefined`.

On the second call, it hasn't cached a value, but it has recorded the [_lastUpdated](https://searchfox.org/mozilla-central/rev/0c2945ad4769e2d4428c72e6ddd78d60eb920394/browser/components/newtab/lib/ASRouterTargeting.jsm#251) time from the first call. So now it [returns early](https://searchfox.org/mozilla-central/rev/0c2945ad4769e2d4428c72e6ddd78d60eb920394/browser/components/newtab/lib/ASRouterTargeting.jsm#246), before it can throw. The return value is `null` since that's the default value.

So in practice, this means that while offline, the value of `needsUpdate` is (almost) always falsy. The only time this matters is on the very first time the targeting attribute is used — on ASRouter initialization. So we may as well just return false instead of throwing. It reduces the risk of something going wrong, and it shouldn't change the results of any real message's targeting evaluation.