Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added special-pages/01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added special-pages/02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added special-pages/03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added special-pages/end.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added special-pages/end2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,8 @@ export function useCustomizer({ title, id, icon, toggle, visibility, index }) {

useEffect(() => {
window.dispatchEvent(new Event(UPDATE_EVENT));
return () => {
window.dispatchEvent(new Event(UPDATE_EVENT));
};
}, [visibility]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export class CustomizerPage {
this.ntp = ntp;
}

context = () => this.ntp.page.locator('aside');

async showsColorSelectionPanel() {
const { page } = this.ntp;
await page.locator('aside').getByLabel('Solid Colors').click();
Expand Down Expand Up @@ -459,4 +461,34 @@ export class CustomizerPage {
button: 'right',
});
}

/**
* @param {string} name
* @returns {Promise<void>}
*/
async isChecked(name) {
await expect(this.context().getByRole('switch', { name })).toBeChecked();
}

/**
* @param {string} name
* @returns {Promise<void>}
*/
async isUnchecked(name) {
await expect(this.context().getByRole('switch', { name })).not.toBeChecked({ timeout: 1000 });
}

/**
* @param {string} name
*/
async hasSwitch(name) {
await expect(this.context().getByRole('switch', { name })).toBeVisible();
}

/**
* @param {string} name
*/
async doesntHaveSwitch(name) {
await expect(this.context().getByRole('switch', { name })).not.toBeVisible();
}
}
1 change: 0 additions & 1 deletion special-pages/pages/new-tab/app/customizer/mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export function customizerMockTransport() {
case 'customizer_onBackgroundUpdate':
case 'customizer_onImagesUpdate': {
subscriptions.set(sub, cb);
console.log('did add sub', sub);
return () => {
console.log('-- did remove sub', sub);
return subscriptions.delete(sub);
Expand Down
10 changes: 9 additions & 1 deletion special-pages/pages/new-tab/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { CustomizerService } from './customizer/customizer.service.js';
import { InlineErrorBoundary } from './InlineErrorBoundary.js';
import { DocumentVisibilityProvider } from '../../../shared/components/DocumentVisibility.js';
import { applyDefaultStyles } from './customizer/utils.js';
import { TabsService } from './tabs/tabs.service.js';
import { TabsDebug, TabsProvider } from './tabs/TabsProvider.js';
import { PersistentScrollProvider } from './tabs/ScrollRestore.js';

/**
* @import {Telemetry} from "./telemetry/telemetry.js"
Expand Down Expand Up @@ -89,6 +92,7 @@ export async function init(root, messaging, telemetry, baseEnvironment) {

// Resolve the entry points for each selected widget
const entryPoints = await resolveEntryPoints(init.widgets, didCatch);
const tabs = new TabsService(messaging, init.tabs || TabsService.DEFAULT);

// Create an instance of the global widget api
const widgetConfigAPI = new WidgetConfigService(messaging, init.widgetConfigs);
Expand Down Expand Up @@ -129,7 +133,11 @@ export async function init(root, messaging, telemetry, baseEnvironment) {
widgets={init.widgets}
entryPoints={entryPoints}
>
<App />
<TabsProvider service={tabs}>
<PersistentScrollProvider />
{environment.urlParams.has('tabs.debug') && <TabsDebug />}
<App />
</TabsProvider>
</WidgetConfigProvider>
</DocumentVisibilityProvider>
</CustomizerProvider>
Expand Down
137 changes: 75 additions & 62 deletions special-pages/pages/new-tab/app/mock-transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { freemiumPIRDataExamples } from './freemium-pir-banner/mocks/freemiumPIR
import { activityMockTransport } from './activity/mocks/activity.mock-transport.js';
import { protectionsMockTransport } from './protections/mocks/protections.mock-transport.js';
import { omnibarMockTransport } from './omnibar/mocks/omnibar.mock-transport.js';
import { tabsMockTransport } from './tabs/tabs.mock-transport.js';

/**
* @typedef {import('../types/new-tab').Favorite} Favorite
Expand Down Expand Up @@ -119,6 +120,7 @@ export function mockTransport() {
activity: activityMockTransport(),
protections: protectionsMockTransport(),
omnibar: omnibarMockTransport(),
tabs: tabsMockTransport(),
};

return new TestTransportConfig({
Expand Down Expand Up @@ -492,68 +494,7 @@ export function mockTransport() {
return Promise.resolve(fromStorage);
}
case 'initialSetup': {
/** @type {import('../types/new-tab.ts').Widgets} */
const widgetsFromStorage = read('widgets') || [
{ id: 'updateNotification' },
{ id: 'rmf' },
{ id: 'freemiumPIRBanner' },
{ id: 'nextSteps' },
{ id: 'favorites' },
];

/** @type {import('../types/new-tab.ts').WidgetConfigs} */
const widgetConfigFromStorage = read('widget_config') || [{ id: 'favorites', visibility: 'visible' }];

/** @type {UpdateNotificationData} */
let updateNotification = { content: null };
const isDelayed = url.searchParams.has('update-notification-delay');

if (!isDelayed && url.searchParams.has('update-notification')) {
const value = url.searchParams.get('update-notification');
if (value && value in updateNotificationExamples) {
updateNotification = updateNotificationExamples[value];
}
}

/** @type {import('../types/new-tab.ts').InitialSetupResponse} */
const initial = {
widgets: widgetsFromStorage,
widgetConfigs: widgetConfigFromStorage,
platform: { name: 'integration' },
env: 'development',
locale: 'en',
updateNotification,
};

widgetsFromStorage.push({ id: 'protections' });
widgetConfigFromStorage.push({ id: 'protections', visibility: 'visible' });

if (url.searchParams.has('omnibar')) {
const favoritesWidgetIndex = widgetsFromStorage.findIndex((widget) => widget.id === 'favorites') ?? 0;
widgetsFromStorage.splice(favoritesWidgetIndex, 0, { id: 'omnibar' });
const favoritesWidgetConfigIndex = widgetConfigFromStorage.findIndex((widget) => widget.id === 'favorites') ?? 0;
widgetConfigFromStorage.splice(favoritesWidgetConfigIndex, 0, { id: 'omnibar', visibility: 'visible' });
}

initial.customizer = customizerData();

/** @type {import('../types/new-tab').NewTabPageSettings} */
const settings = {
customizerDrawer: { state: 'enabled' },
};

if (url.searchParams.get('autoOpen') === 'true' && settings.customizerDrawer) {
settings.customizerDrawer.autoOpen = true;
}

if (url.searchParams.get('adBlocking') === 'enabled') {
settings.adBlocking = { state: 'enabled' };
}

// feature flags
initial.settings = settings;

return Promise.resolve(initial);
return Promise.resolve(initialSetup(url));
}
default: {
return Promise.reject(new Error('unhandled request' + msg));
Expand All @@ -563,6 +504,78 @@ export function mockTransport() {
});
}

/**
* @param {URL} url
* @return {import('../types/new-tab').InitialSetupResponse}
*/
export function initialSetup(url) {
/** @type {import('../types/new-tab.ts').Widgets} */
const widgetsFromStorage = [
{ id: 'updateNotification' },
{ id: 'rmf' },
{ id: 'freemiumPIRBanner' },
{ id: 'nextSteps' },
{ id: 'favorites' },
];

/** @type {import('../types/new-tab.ts').WidgetConfigs} */
const widgetConfigFromStorage = [{ id: 'favorites', visibility: 'visible' }];

/** @type {UpdateNotificationData} */
let updateNotification = { content: null };
const isDelayed = url.searchParams.has('update-notification-delay');

if (!isDelayed && url.searchParams.has('update-notification')) {
const value = url.searchParams.get('update-notification');
if (value && value in updateNotificationExamples) {
updateNotification = updateNotificationExamples[value];
}
}

/** @type {import('../types/new-tab.ts').InitialSetupResponse} */
const initial = {
widgets: widgetsFromStorage,
widgetConfigs: widgetConfigFromStorage,
platform: { name: 'integration' },
env: 'development',
locale: 'en',
updateNotification,
};

widgetsFromStorage.push({ id: 'protections' });
widgetConfigFromStorage.push({ id: 'protections', visibility: 'visible' });

if (url.searchParams.has('omnibar')) {
const favoritesWidgetIndex = widgetsFromStorage.findIndex((widget) => widget.id === 'favorites') ?? 0;
widgetsFromStorage.splice(favoritesWidgetIndex, 0, { id: 'omnibar' });
const favoritesWidgetConfigIndex = widgetConfigFromStorage.findIndex((widget) => widget.id === 'favorites') ?? 0;
widgetConfigFromStorage.splice(favoritesWidgetConfigIndex, 0, { id: 'omnibar', visibility: 'visible' });
}

initial.customizer = customizerData();

/** @type {import('../types/new-tab').NewTabPageSettings} */
const settings = {
customizerDrawer: { state: 'enabled' },
};

if (url.searchParams.get('autoOpen') === 'true' && settings.customizerDrawer) {
settings.customizerDrawer.autoOpen = true;
}

if (url.searchParams.get('adBlocking') === 'enabled') {
settings.adBlocking = { state: 'enabled' };
}

if (url.searchParams.has('tabs')) {
initial.tabs = { tabId: '01', tabIds: ['01'] };
}

// feature flags
initial.settings = settings;
return initial;
}

/**
* @template {{id: string}} T
* @param {T[]} array
Expand Down
1 change: 1 addition & 0 deletions special-pages/pages/new-tab/app/new-tab.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ children:
- ./customizer/customizer.md
- ./protections/protections.md
- ./omnibar/omnibar.md
- ./tabs/tabs.md
---

## Requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SearchForm } from './SearchForm';
import { SearchFormProvider } from './SearchFormProvider';
import { SuggestionsList } from './SuggestionsList';
import { TabSwitcher } from './TabSwitcher';
import { useQueryWithLocalPersistence } from './PersistentOmnibarValuesProvider.js';

/**
* @typedef {import('../strings.json')} Strings
Expand All @@ -23,11 +24,12 @@ import { TabSwitcher } from './TabSwitcher';
* @param {OmnibarConfig['mode']} props.mode
* @param {(mode: OmnibarConfig['mode']) => void} props.setMode
* @param {boolean} props.enableAi
* @param {string|null|undefined} props.tabId
*/
export function Omnibar({ mode, setMode, enableAi }) {
export function Omnibar({ mode, setMode, enableAi, tabId }) {
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));

const [query, setQuery] = useState(/** @type {String} */ (''));
const [query, setQuery] = useQueryWithLocalPersistence(tabId);
const [resetKey, setResetKey] = useState(0);
const [autoFocus, setAutoFocus] = useState(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { useVisibility } from '../../widget-list/widget-config.provider.js';
import { Omnibar } from './Omnibar.js';
import { OmnibarContext } from './OmnibarProvider.js';
import { ArrowIndentCenteredIcon } from '../../components/Icons.js';
import { useModeWithLocalPersistence } from './PersistentOmnibarValuesProvider.js';
import { useTabState } from '../../tabs/TabsProvider.js';

/**
* @typedef {import('../strings.json')} Strings
* @typedef {import('../../../types/new-tab.js').OmnibarConfig} OmnibarConfig
* @typedef {import('../../../types/new-tab.js').OmnibarMode} Mode
*/

/**
Expand All @@ -26,22 +29,27 @@ import { ArrowIndentCenteredIcon } from '../../components/Icons.js';
*/
export function OmnibarConsumer() {
const { state } = useContext(OmnibarContext);
const { current } = useTabState();
if (state.status === 'ready') {
return <OmnibarReadyState config={state.config} />;
return <OmnibarReadyState config={state.config} key={current.value} tabId={current.value} />;
}
return null;
}

/**
* @param {object} props
* @param {OmnibarConfig} props.config
* @param {string} props.tabId
*/
function OmnibarReadyState({ config: { enableAi = true, showAiSetting = true, mode } }) {
function OmnibarReadyState({ config, tabId }) {
const { enableAi = true, showAiSetting = true, mode: defaultMode } = config;
const { setEnableAi, setMode } = useContext(OmnibarContext);
const modeForCurrentTab = useModeWithLocalPersistence(tabId, defaultMode);

return (
<>
{showAiSetting && <AiSetting enableAi={enableAi} setEnableAi={setEnableAi} />}
<Omnibar mode={mode} setMode={setMode} enableAi={showAiSetting && enableAi} />
<Omnibar mode={modeForCurrentTab} setMode={setMode} enableAi={showAiSetting && enableAi} tabId={tabId} />
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { h } from 'preact';

import { OmnibarConsumer } from './OmnibarConsumer.js';
import { SearchIcon } from '../../components/Icons.js';
import { PersistentModeProvider, PersistentTextInputProvider } from './PersistentOmnibarValuesProvider.js';

/**
* @import enStrings from "../strings.json"
Expand All @@ -30,13 +31,15 @@ export function OmnibarCustomized() {

useCustomizer({ title: sectionTitle, id, icon: <SearchIcon />, toggle, visibility: visibility.value, index });

if (visibility.value === 'hidden') {
return null;
}

return (
<OmnibarProvider>
<OmnibarConsumer />
</OmnibarProvider>
<PersistentTextInputProvider>
<PersistentModeProvider>
{visibility.value === 'visible' && (
<OmnibarProvider>
<OmnibarConsumer />
</OmnibarProvider>
)}
</PersistentModeProvider>
</PersistentTextInputProvider>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, h } from 'preact';
import { useCallback, useEffect, useReducer, useRef } from 'preact/hooks';
import { useCallback, useContext, useEffect, useReducer, useRef } from 'preact/hooks';
import { useMessaging } from '../../types.js';
import { reducer, useInitialDataAndConfig, useConfigSubscription } from '../../service.hooks.js';
import { OmnibarService } from '../omnibar.service.js';
Expand Down Expand Up @@ -153,7 +153,7 @@ export function OmnibarProvider(props) {
/**
* @return {import("preact").RefObject<OmnibarService>}
*/
export function useService() {
function useService() {
const service = useRef(/** @type {OmnibarService|null} */ (null));
const ntp = useMessaging();
useEffect(() => {
Expand All @@ -165,3 +165,7 @@ export function useService() {
}, [ntp]);
return service;
}

export function useOmnibarService() {
return useContext(OmnibarServiceContext);
}
Loading
Loading