|
| 1 | +# Activation Window Feature |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The Activation Window feature allows the browser to experiment with the first hours of a new profile's lifetime by temporarily setting different defaults during a configurable time period (typically 48 hours). This enables: |
| 6 | + |
| 7 | +- Hiding or showing specific content sections (top sites, top stories) during the activation window |
| 8 | +- Displaying messaging when entering or exiting the activation window |
| 9 | +- Reverting to normal defaults after the activation window expires |
| 10 | +- Preserving user preference changes made during the activation window |
| 11 | + |
| 12 | +The feature is controlled via Nimbus experiments using the `newtabTrainhop` feature with `type: "activationWindowBehavior"`. |
| 13 | + |
| 14 | +## How It Works |
| 15 | + |
| 16 | +### High-level Architecture |
| 17 | + |
| 18 | +1. **Profile Creation Time Tracking** (`AboutNewTab.sys.mjs`) |
| 19 | + - The browser computes the profile creation instant on startup using `ProfileAge.sys.mjs` |
| 20 | + - The `createdInstant` is cached on the ActivityStream instance for the session |
| 21 | + |
| 22 | +2. **Activation Window Evaluation** (`PrefsFeed.sys.mjs`) |
| 23 | + - `checkForActivationWindow()` runs on: |
| 24 | + - PrefsFeed initialization (startup) |
| 25 | + - Each NEW_TAB_STATE_REQUEST action (when opening a new tab) |
| 26 | + - It compares the current time against the profile age to determine if we're within the activation window |
| 27 | + - Enters or exits activation window state as needed |
| 28 | + |
| 29 | +3. **Default Pref Manipulation** |
| 30 | + - When entering the activation window: Sets default branch prefs for top sites/stories to experiment values |
| 31 | + - When exiting the activation window: Restores default branch prefs to original values |
| 32 | + - User pref values _always_ override defaults, even after enabling and then re-disabling. |
| 33 | + |
| 34 | +4. **User Preference Tracking** |
| 35 | + - Tracks user preference changes during the activation window |
| 36 | + - On exit, ensures that any user changes are persisted |
| 37 | + |
| 38 | +5. **State Broadcasting** |
| 39 | + - Pref changes are broadcast to all content processes |
| 40 | + - StartupCacheInit queues changes for the cached about:home page if it exists |
| 41 | + |
| 42 | +6. **Messaging Integration** |
| 43 | + - PrefsFeed sets message ID prefs on enter/exit: `activationWindow.enterMessageID` and `activationWindow.exitMessageID` |
| 44 | + - ASRouter messages can target these prefs using JEXL expressions |
| 45 | + - The `ActivationWindowMessage` component renders messages with bespoke UI (card layout with image, heading, message, and buttons) |
| 46 | + |
| 47 | +## Configuration |
| 48 | + |
| 49 | +### Nimbus Configuration Schema |
| 50 | + |
| 51 | +The activation window is configured via Nimbus using the `newtabTrainhop` feature: |
| 52 | + |
| 53 | +```javascript |
| 54 | +{ |
| 55 | + featureId: "newtabTrainhop", |
| 56 | + value: { |
| 57 | + type: "activationWindowBehavior", |
| 58 | + payload: { |
| 59 | + enabled: true, |
| 60 | + maxProfileAgeInHours: 48, |
| 61 | + disableTopSites: true, |
| 62 | + disableTopStories: true, |
| 63 | + variant: "a", |
| 64 | + enterActivationWindowMessageID: "ACTIVATION_WINDOW_WELCOME_V1", |
| 65 | + exitActivationWindowMessageID: "ACTIVATION_WINDOW_EXIT_V1" |
| 66 | + } |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +#### Configuration Fields |
| 72 | + |
| 73 | +- **`enabled`** (boolean, default: false): Whether the activation window feature is active |
| 74 | +- **`maxProfileAgeInHours`** (number, default: 48): Duration of the activation window in hours |
| 75 | +- **`disableTopSites`** (boolean, default: false): Hide top sites section during activation window |
| 76 | +- **`disableTopStories`** (boolean, default: false): Hide top stories section during activation window |
| 77 | +- **`variant`** (string, default: ""): Experiment variant identifier |
| 78 | +- **`enterActivationWindowMessageID`** (string, default: ""): Message ID to show when entering the window |
| 79 | +- **`exitActivationWindowMessageID`** (string, default: ""): Message ID to show when exiting the window |
| 80 | + |
| 81 | +### Message Structure |
| 82 | + |
| 83 | +Messages for the activation window use the `ActivationWindowMessage` component and follow this schema: |
| 84 | + |
| 85 | +```javascript |
| 86 | +{ |
| 87 | + id: "MESSAGE_ID", |
| 88 | + template: "newtab_message", |
| 89 | + content: { |
| 90 | + messageType: "ActivationWindowMessage", |
| 91 | + |
| 92 | + // Heading: plain string or Fluent ID |
| 93 | + heading: "Welcome to Your New Tab!", |
| 94 | + // OR |
| 95 | + heading: { string_id: "activation-window-welcome-heading-fluent-id" }, |
| 96 | + |
| 97 | + // Message: plain string or Fluent ID |
| 98 | + message: "We've personalized your experience...", |
| 99 | + // OR |
| 100 | + message: { string_id: "activation-window-welcome-message-fluent-id" }, |
| 101 | + |
| 102 | + // Image (optional, defaults to kit-in-circle.svg if not provided) |
| 103 | + imageSrc: "chrome://newtab/content/data/content/assets/kit.png", |
| 104 | + |
| 105 | + primaryButton: { |
| 106 | + // Plain text label (for tests) |
| 107 | + label: "Learn More", |
| 108 | + // OR Fluent ID label (for production) |
| 109 | + label: { string_id: "activation-window-primary-button-fluent-id" }, |
| 110 | + |
| 111 | + action: { |
| 112 | + type: "SHOW_PERSONALIZE" |
| 113 | + } |
| 114 | + }, |
| 115 | + |
| 116 | + secondaryButton: { |
| 117 | + label: "Dismiss", |
| 118 | + // OR |
| 119 | + label: { string_id: "activation-window-secondary-button-fluent-id" }, |
| 120 | + |
| 121 | + action: { dismiss: true } |
| 122 | + } |
| 123 | + }, |
| 124 | + trigger: { id: "newtabMessageCheck" }, |
| 125 | + targeting: `'browser.newtabpage.activity-stream.activationWindow.enterMessageID' | preferenceValue == 'MESSAGE_ID'`, |
| 126 | + groups: [] |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +#### Message Content Fields |
| 131 | + |
| 132 | +- **`messageType`** (string, required): Must be `"ActivationWindowMessage"` |
| 133 | +- **`heading`** (string or object, optional): Heading text |
| 134 | + - Plain string: `"Welcome to Firefox"` |
| 135 | + - Fluent ID: `{ string_id: "activation-window-heading-fluent-id" }` |
| 136 | +- **`message`** (string or object, optional): Message text |
| 137 | + - Plain string: `"We've personalized your experience"` |
| 138 | + - Fluent ID: `{ string_id: "activation-window-message-fluent-id" }` |
| 139 | +- **`imageSrc`** (string, optional): Chrome URL to image displayed in the message. If not provided, defaults to `"chrome://newtab/content/data/content/assets/kit-in-circle.svg"` |
| 140 | +- **`primaryButton`** (object, optional): Configuration for primary button. |
| 141 | +- **`secondaryButton`** (object, optional): Configuration for secondary button |
| 142 | + |
| 143 | +#### Button Configuration |
| 144 | + |
| 145 | +Each button object has: |
| 146 | + |
| 147 | +- **`label`** (string or object, required): |
| 148 | + - Plain string for test messages: `"Click Me"` |
| 149 | + - Fluent object for production: `{ string_id: "button-label-id-fluent-id" }` |
| 150 | +- **`action`** (object, required): ASRouter action specification |
| 151 | + - `{ dismiss: true }` - Dismiss and block the message |
| 152 | + - `{ type: "SHOW_PERSONALIZE" }` - Open personalization panel |
| 153 | + - More actions may be added in the future. |
| 154 | + |
| 155 | +## Testing Locally |
| 156 | + |
| 157 | +### Using PanelTestProvider |
| 158 | + |
| 159 | +Test messages are available in `browser/components/asrouter/modules/PanelTestProvider.sys.mjs`: |
| 160 | + |
| 161 | +- `TEST_ACTIVATION_WINDOW_ENTER_MESSAGE` |
| 162 | +- `TEST_ACTIVATION_WINDOW_EXIT_MESSAGE` |
| 163 | + |
| 164 | +To test these messages: |
| 165 | + |
| 166 | +1. Open `about:newtab#asrouter` in Firefox |
| 167 | +2. Find either the enter or exit message in the message list |
| 168 | +3. Modify any of the parameters in the message as you'd like |
| 169 | +4. Click "Show" or "Modify" to display the message |
| 170 | +5. Open a new tab |
0 commit comments