Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ web-ext.config.ts
*.njsproj
*.sln
*.sw?

# Scratch and raw store captures
.tmp
store/raw
14 changes: 11 additions & 3 deletions e2e/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,9 @@ test('full preview executes public repository scripts only inside sandbox', asyn
);
const sandbox = await sandboxElement?.contentFrame();
expect(sandbox).not.toBeNull();
// The frame fills the tab below the header instead of the 150px default.
const frameBox = await sandboxElement!.boundingBox();
expect(frameBox!.height).toBeGreaterThan(fullPage.viewportSize()!.height / 2);
await expect
.poll(() => requested.includes(`${rawBase}/reports/weekly/app.js`))
.toBe(true);
Expand Down Expand Up @@ -1574,7 +1577,7 @@ test('popup enable setting updates an already-open GitHub page', async () => {
const popup = await context.newPage();
await popup.goto(`chrome-extension://${extensionId}/popup.html`);
const enabled = popup.getByRole('checkbox', {
name: 'Show previews on GitHub HTML files and pull requests',
name: 'Show previews',
});
await expect(enabled).toBeChecked();
await enabled.uncheck();
Expand Down Expand Up @@ -1852,10 +1855,15 @@ test('blob view choice survives different files and reloads without loading hidd
await settings.goto('chrome-extension://' + extensionId + '/popup.html');
await page.goto(firstUrl, { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('tab', { name: 'Preview' })).toHaveAttribute('aria-selected', 'true');
// Primer paints the selected segment from aria-pressed.
await expect(page.getByRole('tab', { name: 'Preview' })).toHaveAttribute('aria-pressed', 'true');
await expect(page.getByRole('button', { name: 'Code', exact: true })).toHaveAttribute('aria-pressed', 'false');
await expect(page.locator('iframe[title="Executable HTML preview"]').contentFrame().locator('#view-title')).toHaveText('Saved view');
expect(await settings.evaluate("chrome.storage.local.get('blobViewMode')")).toEqual({});

await page.getByRole('button', { name: 'Code', exact: true }).click();
await expect(page.getByRole('button', { name: 'Code', exact: true })).toHaveAttribute('aria-pressed', 'true');
await expect(page.getByRole('tab', { name: 'Preview' })).toHaveAttribute('aria-pressed', 'false');
await expect.poll(() => settings.evaluate("chrome.storage.local.get('blobViewMode')")).toEqual({ blobViewMode: 'source' });
const requestsBeforeCode = scriptRequests;
// GitHub replaces its DOM while keeping the extension's content script alive.
Expand Down Expand Up @@ -1979,12 +1987,12 @@ function githubBlobFixture(
<div class="react-blob-view-header-sticky">
<ul class="SegmentedControl" aria-label="File view">
<li class="native-segment" data-selected data-component="SegmentedControl.Button">
<button class="native-segment-button" type="button" aria-current="true" style="--separator-color:transparent">
<button class="native-segment-button" type="button" aria-pressed="true" style="--separator-color:transparent">
<span class="segmentedControl-content"><span class="segmentedControl-text" data-text="Code">Code</span></span>
</button>
</li>
<li class="native-segment" data-component="SegmentedControl.Button">
<button class="native-segment-button" type="button" aria-current="false" style="--separator-color:var(--borderColor-default)">
<button class="native-segment-button" type="button" aria-pressed="false" style="--separator-color:var(--borderColor-default)">
<span class="segmentedControl-content"><span class="segmentedControl-text" data-text="Blame">Blame</span></span>
</button>
</li>
Expand Down
31 changes: 20 additions & 11 deletions entrypoints/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface RouteState {
readonly tab: HTMLElement;
readonly tabButton: HTMLButtonElement;
readonly tabBar: HTMLElement;
readonly nativeSelectedButton: HTMLButtonElement | null;
readonly nativeListenerCleanup: Array<() => void>;
readonly container: HTMLElement;
readonly previewArea: HTMLElement;
Expand Down Expand Up @@ -80,6 +81,9 @@ export default defineContentScript({
state.render?.destroy();
void removePreviewSnapshot(state.snapshotId);
state.nativeListenerCleanup.forEach((cleanup) => cleanup());
if (state.active && state.tabBar.isConnected) {
setSelectedTab(state.tabBar, state.nativeSelectedButton);
}
if (state.codeView.isConnected) state.codeView.style.removeProperty('display');
state.tab.remove();
state.container.remove();
Expand All @@ -92,23 +96,17 @@ export default defineContentScript({
void blobViewModeStorage.setValue(next);
};

const showCode = () => {
const showCode = (nativeButton: HTMLButtonElement) => {
if (!state) return;
state.active = false;
if (state.resolving && !state.render) {
state.controller.abort();
state.controller = new AbortController();
state.resolving = null;
}
// Only deselect the Preview tab; GitHub's React state manages native
// tab buttons and overrides aria-current/data-selected on re-render.
state.tabButton.setAttribute('aria-current', 'false');
state.tabButton.setAttribute('aria-selected', 'false');
state.tabButton.style.setProperty(
'--separator-color',
'var(--borderColor-default)',
);
state.tab.removeAttribute('data-selected');
// React only writes aria-pressed when its own selection changes, so a
// click on the already-selected native tab must restore it here.
setSelectedTab(state.tabBar, nativeButton);
state.container.style.setProperty('display', 'none', 'important');
state.codeView.style.removeProperty('display');
debugLog('blob', 'show-code', { path: state.repoRef.path });
Expand Down Expand Up @@ -184,6 +182,13 @@ export default defineContentScript({
container.append(header, details, previewArea);
blob.appendChild(container);

const nativeSelectedButton =
Array.from(tabBar.querySelectorAll<HTMLButtonElement>('button')).find(
(candidate) =>
candidate !== button &&
(candidate.getAttribute('aria-pressed') === 'true' ||
candidate.getAttribute('aria-current') === 'true'),
) ?? null;
const nativeListenerCleanup: Array<() => void> = [];
state = {
generation: routeGeneration,
Expand All @@ -196,6 +201,7 @@ export default defineContentScript({
tab,
tabButton: button,
tabBar,
nativeSelectedButton,
nativeListenerCleanup,
container,
previewArea,
Expand Down Expand Up @@ -223,7 +229,7 @@ export default defineContentScript({
if (nativeButton === button) continue;
const listener = () => {
rememberViewMode('source');
showCode();
showCode(nativeButton);
};
nativeButton.addEventListener('click', listener);
nativeListenerCleanup.push(() =>
Expand Down Expand Up @@ -582,6 +588,7 @@ function createPreviewTab(
button.setAttribute('aria-controls', previewId);
button.setAttribute('aria-selected', 'false');
button.setAttribute('aria-current', 'false');
button.setAttribute('aria-pressed', 'false');
button.style.setProperty(
'--separator-color',
'var(--borderColor-default)',
Expand Down Expand Up @@ -612,6 +619,8 @@ function setSelectedTab(
tabBar.querySelectorAll<HTMLButtonElement>('button'),
)) {
const selected = button === selectedButton;
// Primer styles the selected segment from aria-pressed.
button.setAttribute('aria-pressed', String(selected));
button.setAttribute('aria-current', String(selected));
if (button.closest('li')?.classList.contains(PREVIEW_TAB_CLASS)) {
button.setAttribute('aria-selected', String(selected));
Expand Down
Loading
Loading