-
Notifications
You must be signed in to change notification settings - Fork 39
Fix moz-extension:// navigation hang by bypassing geckodriver #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
4a6c829
fix: use BiDi navigate for moz-extension:// URLs to avoid hang
csamu 357f469
test: add unit and integration tests for moz-extension:// navigation fix
csamu e8d1e4d
Code style: break up long constructors into several lines
csamu bace8c2
refactor: use BiDi navigate for all URLs, not just moz-extension://
csamu 3ea0d81
test: remove raceWithTimeout from integration tests
csamu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| /** | ||
| * Unit tests for PageManagement and isCommonScheme | ||
| * | ||
| * Navigation uses BiDi browsingContext.navigate for all URLs. | ||
| * Common schemes (http/https/data/blob/file) use wait:"interactive". | ||
| * Uncommon schemes (moz-extension:, about:, etc.) use wait:"none" | ||
| */ | ||
|
|
||
| import { describe, it, expect, vi } from 'vitest'; | ||
| import { isCommonScheme, PageManagement } from '@/firefox/pages.js'; | ||
|
|
||
| const HTTPS_URL = 'https://example.com/test.html'; | ||
| const HTTP_URL = 'http://example.com/test.html'; | ||
| const FILE_URL = 'file:///some/path/test.html'; | ||
| const MOZ_EXT_URL = 'moz-extension://a1b2c3d4-e5f6-7890-abcd-ef1234567890/popup.html'; | ||
| const BLOB_URL = 'blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f64'; | ||
| const DATA_URL = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='; | ||
|
|
||
| describe('isCommonScheme', () => { | ||
| it('returns true for common URL schemes', () => { | ||
| expect(isCommonScheme(HTTPS_URL)).toBe(true); | ||
| expect(isCommonScheme(HTTP_URL)).toBe(true); | ||
| expect(isCommonScheme(FILE_URL)).toBe(true); | ||
| expect(isCommonScheme(DATA_URL)).toBe(true); | ||
| expect(isCommonScheme(BLOB_URL)).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false for moz-extension:// URLs', () => { | ||
| expect(isCommonScheme(MOZ_EXT_URL)).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false for about: URLs', () => { | ||
| expect(isCommonScheme('about:blank')).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false for malformed URLs', () => { | ||
| expect(isCommonScheme('not-a-url')).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| // -- PageManagement ----------------------------------------------------------- | ||
|
|
||
| describe('PageManagement', () => { | ||
| function createMocks() { | ||
| // Any driver method call should fail — navigate must use BiDi, not driver | ||
| const driver = new Proxy( | ||
| {}, | ||
| { | ||
| get: () => { | ||
| throw new Error('Unexpected driver call — navigation must use BiDi'); | ||
| }, | ||
| } | ||
| ); | ||
| const getCurrentContextId = vi.fn().mockReturnValue('ctx-1'); | ||
| const setCurrentContextId = vi.fn(); | ||
| const sendBiDiCommand = vi.fn().mockResolvedValue({}); | ||
|
|
||
| const pages = new PageManagement( | ||
| driver, | ||
| getCurrentContextId, | ||
| setCurrentContextId, | ||
| sendBiDiCommand | ||
| ); | ||
| return { pages, sendBiDiCommand }; | ||
| } | ||
|
|
||
| describe('navigate', () => { | ||
| it('uses BiDi with wait:interactive for common URL schemes', async () => { | ||
| const { pages, sendBiDiCommand } = createMocks(); | ||
|
|
||
| await pages.navigate(HTTPS_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: HTTPS_URL, | ||
| wait: 'interactive', | ||
| }); | ||
|
|
||
| await pages.navigate(HTTP_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: HTTP_URL, | ||
| wait: 'interactive', | ||
| }); | ||
|
|
||
| await pages.navigate(DATA_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: DATA_URL, | ||
| wait: 'interactive', | ||
| }); | ||
|
|
||
| await pages.navigate(FILE_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: FILE_URL, | ||
| wait: 'interactive', | ||
| }); | ||
|
|
||
| await pages.navigate(BLOB_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: BLOB_URL, | ||
| wait: 'interactive', | ||
| }); | ||
| }); | ||
|
|
||
| it('uses BiDi with wait:none for uncommon URL schemes', async () => { | ||
| const { pages, sendBiDiCommand } = createMocks(); | ||
|
|
||
| await pages.navigate(MOZ_EXT_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: MOZ_EXT_URL, | ||
| wait: 'none', | ||
| }); | ||
|
|
||
| await pages.navigate('about:blank'); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: 'about:blank', | ||
| wait: 'none', | ||
| }); | ||
|
|
||
| await pages.navigate('not-a-url'); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'ctx-1', | ||
| url: 'not-a-url', | ||
| wait: 'none', | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('createNewPage', () => { | ||
| it('uses BiDi navigate', async () => { | ||
| // createNewPage needs real driver methods for switchTo/handles | ||
| const switchToMock = vi | ||
| .fn() | ||
| .mockReturnValue({ newWindow: vi.fn().mockResolvedValue(undefined) }); | ||
| const getAllWindowHandlesMock = vi.fn().mockResolvedValue(['handle-1', 'handle-2']); | ||
|
|
||
| const driver = { | ||
| switchTo: switchToMock, | ||
| getAllWindowHandles: getAllWindowHandlesMock, | ||
| } as any; | ||
|
|
||
| const getCurrentContextId = vi.fn().mockReturnValue('handle-2'); | ||
| const setCurrentContextId = vi.fn(); | ||
| const sendBiDiCommand = vi.fn().mockResolvedValue({}); | ||
|
|
||
| const pages = new PageManagement( | ||
| driver, | ||
| getCurrentContextId, | ||
| setCurrentContextId, | ||
| sendBiDiCommand | ||
| ); | ||
|
|
||
| // wait: interactive | ||
| await pages.createNewPage(HTTPS_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'handle-2', | ||
| url: HTTPS_URL, | ||
| wait: 'interactive', | ||
| }); | ||
|
|
||
| // wait: none | ||
| await pages.createNewPage(MOZ_EXT_URL); | ||
| expect(sendBiDiCommand).toHaveBeenCalledWith('browsingContext.navigate', { | ||
| context: 'handle-2', | ||
| url: MOZ_EXT_URL, | ||
| wait: 'none', | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "manifest_version": 2, | ||
| "name": "MCP Test Extension", | ||
| "version": "1.0", | ||
| "description": "Minimal extension for testing moz-extension:// navigation", | ||
| "browser_action": { | ||
| "default_popup": "popup.html" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <title>MCP Test Extension</title> | ||
| </head> | ||
| <body> | ||
| <h1>MCP Test Extension</h1> | ||
| <p id="status">extension-loaded</p> | ||
| </body> | ||
| </html> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be completely transparent here, maybe
completewould be a more faithful translation of the current implementation. However BiDi is much more strict than Classic when it comes to navigation, so I prefer to stick with interactive for now.