commit 7f79b85c2a365b04978c0754b960a295621c91e3
parent a06e07a18962bfceed8da6aeec09a6e613bef318
Author: Tom Zhang <tzhang@mozilla.com>
Date: Tue, 30 Dec 2025 01:53:54 +0000
Bug 2006965 - get_open_tabs and get_page_content to only check tabs from ai-window mode r=Mardak,ai-models-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D277618
Diffstat:
5 files changed, 137 insertions(+), 576 deletions(-)
diff --git a/browser/components/aiwindow/models/Tools.sys.mjs b/browser/components/aiwindow/models/Tools.sys.mjs
@@ -13,6 +13,8 @@ import { PageExtractorParent } from "resource://gre/actors/PageExtractorParent.s
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ AIWindow:
+ "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
PageDataService:
"moz-src:///browser/components/pagedata/PageDataService.sys.mjs",
@@ -111,22 +113,25 @@ export const toolsConfig = [
export async function getOpenTabs(n = 15) {
const tabs = [];
- const win = lazy.BrowserWindowTracker.getTopWindow();
- if (!win || win.closed || !win.gBrowser) {
- return [];
- }
+ for (const win of lazy.BrowserWindowTracker.orderedWindows) {
+ if (!lazy.AIWindow.isAIWindowActive(win)) {
+ continue;
+ }
- for (const tab of win.gBrowser.tabs) {
- const browser = tab.linkedBrowser;
- const url = browser?.currentURI?.spec;
- const title = tab.label;
-
- if (url && !url.startsWith("about:")) {
- tabs.push({
- url,
- title,
- lastAccessed: tab.lastAccessed,
- });
+ if (!win.closed && win.gBrowser) {
+ for (const tab of win.gBrowser.tabs) {
+ const browser = tab.linkedBrowser;
+ const url = browser?.currentURI?.spec;
+ const title = tab.label;
+
+ if (url && !url.startsWith("about:")) {
+ tabs.push({
+ url,
+ title,
+ lastAccessed: tab.lastAccessed,
+ });
+ }
+ }
}
}
@@ -270,36 +275,47 @@ export class GetPageContent {
);
}
- // TODO: figure out what windows we can access to give permission here, and update this API
- let win = lazy.BrowserWindowTracker.getTopWindow();
- let gBrowser = win.gBrowser;
- let tabs = gBrowser.tabs;
-
- // Find the tab with the matching URL in browser
+ // Search through all AI Windows to find the tab with the matching URL
let targetTab = null;
- for (let i = 0; i < tabs.length; i++) {
- const tab = tabs[i];
- const currentURI = tab?.linkedBrowser?.currentURI;
- if (currentURI?.spec === url) {
- targetTab = tab;
- break;
+ for (const win of lazy.BrowserWindowTracker.orderedWindows) {
+ if (!lazy.AIWindow.isAIWindowActive(win)) {
+ continue;
}
- }
- // If no match, try hostname matching for cases where protocols differ
- if (!targetTab) {
- try {
- const inputHostPort = new URL(url).host;
- targetTab = tabs.find(tab => {
+ if (!win.closed && win.gBrowser) {
+ const tabs = win.gBrowser.tabs;
+
+ // Find the tab with the matching URL in this window
+ for (let i = 0; i < tabs.length; i++) {
+ const tab = tabs[i];
+ const currentURI = tab?.linkedBrowser?.currentURI;
+ if (currentURI?.spec === url) {
+ targetTab = tab;
+ break;
+ }
+ }
+
+ // If no match, try hostname matching for cases where protocols differ
+ if (!targetTab) {
try {
- const tabHostPort = tab.linkedBrowser.currentURI.hostPort;
- return tabHostPort === inputHostPort;
+ const inputHostPort = new URL(url).host;
+ targetTab = tabs.find(tab => {
+ try {
+ const tabHostPort = tab.linkedBrowser.currentURI.hostPort;
+ return tabHostPort === inputHostPort;
+ } catch {
+ return false;
+ }
+ });
} catch {
- return false;
+ // Invalid URL, continue with original logic
}
- });
- } catch {
- // Invalid URL, continue with original logic
+ }
+
+ // If we found the tab, stop searching
+ if (targetTab) {
+ break;
+ }
}
}
diff --git a/browser/components/aiwindow/models/tests/browser/browser.toml b/browser/components/aiwindow/models/tests/browser/browser.toml
@@ -2,5 +2,9 @@
support-files = [
"head.js",
]
+prefs = [
+ "browser.aiwindow.enabled=true",
+]
["browser_get_page_content.js"]
+window_attributes = "ai-window"
diff --git a/browser/components/aiwindow/models/tests/browser/browser_get_page_content.js b/browser/components/aiwindow/models/tests/browser/browser_get_page_content.js
@@ -26,6 +26,20 @@ add_task(async function test_get_page_content_basic() {
const { url, GetPageContent, cleanup } = await setupGetPageContentTest(html);
+ // Manually set the ai-window attribute for testing
+ // (in production this is set via window features when opening the window)
+ window.document.documentElement.setAttribute("ai-window", "true");
+
+ // Verify we're in an AI Window
+ const { AIWindow } = ChromeUtils.importESModule(
+ "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs"
+ );
+ info("Is AI Window: " + AIWindow.isAIWindowActive(window));
+ info(
+ "Window has ai-window attribute: " +
+ window.document.documentElement.hasAttribute("ai-window")
+ );
+
// Create an allowed URLs set containing the test page
const allowedUrls = new Set([url]);
diff --git a/browser/components/aiwindow/models/tests/xpcshell/test_Tools_GetOpenTabs.js b/browser/components/aiwindow/models/tests/xpcshell/test_Tools_GetOpenTabs.js
@@ -22,12 +22,17 @@ function createFakeTab(url, title, lastAccessed) {
};
}
-function createFakeWindow(tabs, closed = false) {
+function createFakeWindow(tabs, closed = false, isAIWindow = true) {
return {
closed,
gBrowser: {
tabs,
},
+ document: {
+ documentElement: {
+ hasAttribute: attr => attr === "ai-window" && isAIWindow,
+ },
+ },
};
}
@@ -65,7 +70,7 @@ add_task(async function test_getOpenTabs_basic() {
createFakeTab("https://firefox.com", "Firefox", 3000),
]);
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(fakeWindow);
+ sb.stub(BrowserWindowTracker, "orderedWindows").get(() => [fakeWindow]);
setupPageDataServiceMock(sb, {
"https://firefox.com": "Firefox browser homepage",
"https://mozilla.org": "Mozilla organization site",
@@ -114,7 +119,7 @@ add_task(async function test_getOpenTabs_filters_about_urls() {
createFakeTab("about:blank", "Blank", 5000),
]);
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(fakeWindow);
+ sb.stub(BrowserWindowTracker, "orderedWindows").get(() => [fakeWindow]);
setupPageDataServiceMock(sb);
const tabs = await getOpenTabs();
@@ -139,7 +144,7 @@ add_task(async function test_getOpenTabs_filters_about_urls() {
}
});
-add_task(async function test_getOpenTabs_limits_to_n() {
+add_task(async function test_getOpenTabs_pagination() {
const BrowserWindowTracker = ChromeUtils.importESModule(
"resource:///modules/BrowserWindowTracker.sys.mjs"
).BrowserWindowTracker;
@@ -155,19 +160,23 @@ add_task(async function test_getOpenTabs_limits_to_n() {
}
const fakeWindow = createFakeWindow(tabs);
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(fakeWindow);
+ sb.stub(BrowserWindowTracker, "orderedWindows").get(() => [fakeWindow]);
setupPageDataServiceMock(sb);
- const result = await getOpenTabs(10);
-
- Assert.equal(result.length, 10, "Should return at most 10 tabs");
+ // Test default limit
+ const defaultResult = await getOpenTabs();
+ Assert.equal(defaultResult.length, 15, "Should default to 15 tabs");
Assert.equal(
- result[0].url,
+ defaultResult[0].url,
"https://example19.com",
"First tab should be most recent"
);
+
+ // Test custom limit
+ const customResult = await getOpenTabs(10);
+ Assert.equal(customResult.length, 10, "Should return at most 10 tabs");
Assert.equal(
- result[9].url,
+ customResult[9].url,
"https://example10.com",
"Last tab should be 10th most recent"
);
@@ -176,68 +185,7 @@ add_task(async function test_getOpenTabs_limits_to_n() {
}
});
-add_task(async function test_getOpenTabs_default_limit() {
- const BrowserWindowTracker = ChromeUtils.importESModule(
- "resource:///modules/BrowserWindowTracker.sys.mjs"
- ).BrowserWindowTracker;
-
- const sb = sinon.createSandbox();
-
- try {
- const tabs = [];
- for (let i = 0; i < 20; i++) {
- tabs.push(
- createFakeTab(`https://example${i}.com`, `Example ${i}`, i * 1000)
- );
- }
- const fakeWindow = createFakeWindow(tabs);
-
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(fakeWindow);
- setupPageDataServiceMock(sb);
-
- const result = await getOpenTabs();
-
- Assert.equal(result.length, 15, "Should default to 15 tabs");
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getOpenTabs_single_window_multiple_tabs() {
- const BrowserWindowTracker = ChromeUtils.importESModule(
- "resource:///modules/BrowserWindowTracker.sys.mjs"
- ).BrowserWindowTracker;
-
- const sb = sinon.createSandbox();
-
- try {
- const window1 = createFakeWindow([
- createFakeTab("https://tab1.com", "Tab1", 1000),
- createFakeTab("https://tab2.com", "Tab2", 2000),
- createFakeTab("https://tab3.com", "Tab3", 3000),
- createFakeTab("https://tab4.com", "Tab4", 4000),
- ]);
-
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(window1);
- setupPageDataServiceMock(sb);
-
- const tabs = await getOpenTabs();
-
- Assert.equal(tabs.length, 4, "Should return all tabs from current window");
- Assert.equal(
- tabs[0].url,
- "https://tab4.com",
- "Most recent tab from current window"
- );
- Assert.equal(tabs[1].url, "https://tab3.com", "Second most recent");
- Assert.equal(tabs[2].url, "https://tab2.com", "Third most recent");
- Assert.equal(tabs[3].url, "https://tab1.com", "Least recent");
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getOpenTabs_closed_window() {
+add_task(async function test_getOpenTabs_filters_non_ai_windows() {
const BrowserWindowTracker = ChromeUtils.importESModule(
"resource:///modules/BrowserWindowTracker.sys.mjs"
).BrowserWindowTracker;
@@ -245,87 +193,43 @@ add_task(async function test_getOpenTabs_closed_window() {
const sb = sinon.createSandbox();
try {
- const closedWindow = createFakeWindow(
- [createFakeTab("https://closed.com", "Closed", 2000)],
+ const aiWindow = createFakeWindow(
+ [
+ createFakeTab("https://ai1.com", "AI Tab 1", 1000),
+ createFakeTab("https://ai2.com", "AI Tab 2", 2000),
+ ],
+ false,
true
);
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(closedWindow);
- setupPageDataServiceMock(sb);
-
- const tabs = await getOpenTabs();
-
- Assert.equal(tabs.length, 0, "Should return empty array for closed window");
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getOpenTabs_window_without_gBrowser() {
- const BrowserWindowTracker = ChromeUtils.importESModule(
- "resource:///modules/BrowserWindowTracker.sys.mjs"
- ).BrowserWindowTracker;
-
- const sb = sinon.createSandbox();
-
- try {
- const windowWithoutGBrowser = {
- closed: false,
- gBrowser: null,
- };
-
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(
- windowWithoutGBrowser
+ const classicWindow = createFakeWindow(
+ [
+ createFakeTab("https://classic1.com", "Classic Tab 1", 3000),
+ createFakeTab("https://classic2.com", "Classic Tab 2", 4000),
+ ],
+ false,
+ false
);
+
+ sb.stub(BrowserWindowTracker, "orderedWindows").get(() => [
+ classicWindow,
+ aiWindow,
+ ]);
setupPageDataServiceMock(sb);
const tabs = await getOpenTabs();
Assert.equal(
tabs.length,
- 0,
- "Should return empty array for window without gBrowser"
+ 2,
+ "Should only return tabs from AI Windows (filtered 2 classic tabs)"
+ );
+ Assert.equal(tabs[0].url, "https://ai2.com", "Most recent AI tab");
+ Assert.equal(tabs[1].url, "https://ai1.com", "Second AI tab");
+ Assert.ok(
+ !tabs.some(t => t.url.includes("classic")),
+ "No classic window tabs in results"
);
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getOpenTabs_empty_window() {
- const BrowserWindowTracker = ChromeUtils.importESModule(
- "resource:///modules/BrowserWindowTracker.sys.mjs"
- ).BrowserWindowTracker;
-
- const sb = sinon.createSandbox();
-
- try {
- const emptyWindow = createFakeWindow([]);
-
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(emptyWindow);
- setupPageDataServiceMock(sb);
-
- const tabs = await getOpenTabs();
-
- Assert.equal(tabs.length, 0, "Should return empty array for no tabs");
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getOpenTabs_no_window() {
- const BrowserWindowTracker = ChromeUtils.importESModule(
- "resource:///modules/BrowserWindowTracker.sys.mjs"
- ).BrowserWindowTracker;
-
- const sb = sinon.createSandbox();
-
- try {
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(null);
- setupPageDataServiceMock(sb);
-
- const tabs = await getOpenTabs();
-
- Assert.equal(tabs.length, 0, "Should return empty array when no window");
} finally {
sb.restore();
}
@@ -343,7 +247,7 @@ add_task(async function test_getOpenTabs_return_structure() {
createFakeTab("https://test.com", "Test Page", 1000),
]);
- sb.stub(BrowserWindowTracker, "getTopWindow").returns(fakeWindow);
+ sb.stub(BrowserWindowTracker, "orderedWindows").get(() => [fakeWindow]);
setupPageDataServiceMock(sb, {
"https://test.com": "A test page description",
});
diff --git a/browser/components/aiwindow/models/tests/xpcshell/test_Tools_GetPageContent.js b/browser/components/aiwindow/models/tests/xpcshell/test_Tools_GetPageContent.js
@@ -42,21 +42,34 @@ function createFakeTab(url, title, hasBrowsingContext = true) {
};
}
-function createFakeWindow(tabs) {
+function createFakeWindow(tabs, closed = false, isAIWindow = true) {
return {
- closed: false,
+ closed,
gBrowser: {
tabs,
},
+ document: {
+ documentElement: {
+ hasAttribute: attr => attr === "ai-window" && isAIWindow,
+ },
+ },
};
}
-function setupBrowserWindowTracker(sandbox, window) {
+function setupBrowserWindowTracker(sandbox, windows) {
const BrowserWindowTracker = ChromeUtils.importESModule(
"resource:///modules/BrowserWindowTracker.sys.mjs"
).BrowserWindowTracker;
- sandbox.stub(BrowserWindowTracker, "getTopWindow").returns(window);
+ let windowArray;
+ if (windows === null) {
+ windowArray = [];
+ } else if (Array.isArray(windows)) {
+ windowArray = windows;
+ } else {
+ windowArray = [windows];
+ }
+ sandbox.stub(BrowserWindowTracker, "orderedWindows").get(() => windowArray);
}
add_task(async function test_getPageContent_exact_url_match() {
@@ -90,35 +103,6 @@ add_task(async function test_getPageContent_exact_url_match() {
}
});
-add_task(async function test_getPageContent_normalized_url_match() {
- const sb = sinon.createSandbox();
-
- try {
- const tabs = [
- createFakeTab("https://example.com/page/", "Example Page"),
- createFakeTab("https://other.com", "Other"),
- ];
-
- setupBrowserWindowTracker(sb, createFakeWindow(tabs));
-
- const result = await GetPageContent.getPageContent(
- { url: "https://example.com/page" },
- new Set(["https://example.com/page"])
- );
-
- Assert.ok(
- result.includes("Example Page"),
- "Should match URL after normalizing trailing slashes"
- );
- Assert.ok(
- result.includes("Sample page content"),
- "Should include page content"
- );
- } finally {
- sb.restore();
- }
-});
-
add_task(async function test_getPageContent_hostname_match() {
const sb = sinon.createSandbox();
@@ -324,53 +308,6 @@ add_task(async function test_getPageContent_content_truncation() {
}
});
-add_task(async function test_getPageContent_truncation_at_sentence_boundary() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com/sentences";
- const sentence = "This is a sentence. ";
- const longContent = sentence.repeat(600);
-
- const mockExtractor = {
- getText: sinon.stub().resolves(longContent),
- getReaderModeContent: sinon.stub().resolves(""),
- };
-
- const tab = createFakeTab(targetUrl, "Sentences");
- tab.linkedBrowser.browsingContext.currentWindowContext.getActor = sinon
- .stub()
- .resolves(mockExtractor);
-
- setupBrowserWindowTracker(sb, createFakeWindow([tab]));
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- const contentMatch = result.match(/Content \(full page\) from.*:\s*(.*)/s);
- Assert.ok(contentMatch, "Should match content pattern");
-
- const extractedContent = contentMatch[1].trim();
- Assert.lessOrEqual(
- extractedContent.length,
- 10001,
- "Should truncate near 10000 chars"
- );
- Assert.ok(
- extractedContent.endsWith("."),
- "Should end at sentence boundary (period)"
- );
- Assert.ok(
- !extractedContent.endsWith("..."),
- "Should not have ... when truncated at sentence"
- );
- } finally {
- sb.restore();
- }
-});
-
add_task(async function test_getPageContent_empty_content() {
const sb = sinon.createSandbox();
@@ -443,39 +380,6 @@ add_task(async function test_getPageContent_extraction_error() {
}
});
-add_task(async function test_getPageContent_viewport_mode() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com/viewport";
-
- const mockExtractor = {
- getText: sinon.stub().resolves("Full page content"),
- getReaderModeContent: sinon.stub().resolves(""),
- };
-
- const tab = createFakeTab(targetUrl, "Viewport Test");
- tab.linkedBrowser.browsingContext.currentWindowContext.getActor = sinon
- .stub()
- .resolves(mockExtractor);
-
- setupBrowserWindowTracker(sb, createFakeWindow([tab]));
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes("Content (full page)"),
- "Should use full mode by default"
- );
- Assert.ok(result.includes("Full page content"), "Should include content");
- } finally {
- sb.restore();
- }
-});
-
add_task(async function test_getPageContent_reader_mode_string() {
const sb = sinon.createSandbox();
@@ -513,121 +417,6 @@ add_task(async function test_getPageContent_reader_mode_string() {
}
});
-add_task(async function test_getPageContent_no_window() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com";
- setupBrowserWindowTracker(sb, null);
-
- // Add URL to allowed list so it checks for window instead of trying headless
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes("Error retrieving content"),
- "Should handle null window gracefully"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getPageContent_closed_window() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com";
- const closedWindow = {
- closed: true,
- gBrowser: { tabs: [] },
- };
-
- setupBrowserWindowTracker(sb, closedWindow);
-
- // Add URL to allowed list so it checks for window instead of trying headless
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes("Error retrieving content") ||
- result.includes("Cannot find URL"),
- "Should handle closed window with error"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getPageContent_window_without_gBrowser() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com";
- const windowWithoutGBrowser = {
- closed: false,
- gBrowser: null,
- };
-
- setupBrowserWindowTracker(sb, windowWithoutGBrowser);
-
- // Add URL to allowed list so it checks for window instead of trying headless
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes("Error retrieving content"),
- "Should handle window without gBrowser"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getPageContent_whitespace_normalization() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com/whitespace";
- const messyContent =
- "Text with lots\n\n\nof whitespace\n\n\n\nhere";
-
- const mockExtractor = {
- getText: sinon.stub().resolves(messyContent),
- getReaderModeContent: sinon.stub().resolves(""),
- };
-
- const tab = createFakeTab(targetUrl, "Whitespace Test");
- tab.linkedBrowser.browsingContext.currentWindowContext.getActor = sinon
- .stub()
- .resolves(mockExtractor);
-
- setupBrowserWindowTracker(sb, createFakeWindow([tab]));
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes("Text with lots of whitespace here"),
- "Should normalize whitespace"
- );
- Assert.ok(
- !result.includes(" "),
- "Should not have multiple consecutive spaces"
- );
- } finally {
- sb.restore();
- }
-});
-
add_task(async function test_getPageContent_invalid_url_format() {
const sb = sinon.createSandbox();
@@ -651,169 +440,3 @@ add_task(async function test_getPageContent_invalid_url_format() {
sb.restore();
}
});
-
-add_task(async function test_getPageContent_extraction_returns_string() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com/string";
- const directString = "Direct string content";
-
- const mockExtractor = {
- getText: sinon.stub().resolves(directString),
- getReaderModeContent: sinon.stub().resolves(""),
- };
-
- const tab = createFakeTab(targetUrl, "String Test");
- tab.linkedBrowser.browsingContext.currentWindowContext.getActor = sinon
- .stub()
- .resolves(mockExtractor);
-
- setupBrowserWindowTracker(sb, createFakeWindow([tab]));
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes(directString),
- "Should handle extraction returning string directly"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getPageContent_extraction_returns_object() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com/object";
- // The API now expects strings, not objects
- // If getText returns a non-string object, it should be treated as no content
- const objectContent = { text: "Object text content" };
-
- const mockExtractor = {
- getText: sinon.stub().resolves(objectContent),
- getReaderModeContent: sinon.stub().resolves(""),
- };
-
- const tab = createFakeTab(targetUrl, "Object Test");
- tab.linkedBrowser.browsingContext.currentWindowContext.getActor = sinon
- .stub()
- .resolves(mockExtractor);
-
- setupBrowserWindowTracker(sb, createFakeWindow([tab]));
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- // API expects strings now, objects are treated as no content
- Assert.ok(
- result.includes("returned no content"),
- "Should treat object return value as no content"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(
- async function test_getPageContent_extraction_returns_non_string_text() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://example.com/nonstring";
-
- const mockExtractor = {
- getText: sinon.stub().resolves(12345),
- getReaderModeContent: sinon.stub().resolves(""),
- };
-
- const tab = createFakeTab(targetUrl, "Non-string Test");
- tab.linkedBrowser.browsingContext.currentWindowContext.getActor = sinon
- .stub()
- .resolves(mockExtractor);
-
- setupBrowserWindowTracker(sb, createFakeWindow([tab]));
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- Assert.ok(
- result.includes("returned no content"),
- "Should handle non-string text property as empty"
- );
- } finally {
- sb.restore();
- }
- }
-);
-
-add_task(async function test_getPageContent_allowed_urls_set() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://allowed.com/page";
- const tabs = [createFakeTab("https://other.com", "Other")];
-
- setupBrowserWindowTracker(sb, createFakeWindow(tabs));
-
- const allowedUrls = new Set([
- "https://allowed.com/page",
- "https://another-allowed.com",
- ]);
-
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- allowedUrls
- );
-
- // Headless extraction doesn't work in xpcshell environment
- Assert.ok(
- result.includes("Cannot find URL"),
- "Should return error when tab not found (headless doesn't work in xpcshell)"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_getPageContent_available_tabs_list() {
- const sb = sinon.createSandbox();
-
- try {
- const targetUrl = "https://notfound.com";
- const tabs = [
- createFakeTab("https://first.com", "First Tab"),
- createFakeTab("https://second.com", "Second Tab"),
- createFakeTab("https://third.com", "Third Tab"),
- createFakeTab("https://fourth.com", "Fourth Tab"),
- ];
-
- setupBrowserWindowTracker(sb, createFakeWindow(tabs));
-
- // Add the URL to allowed list so it searches tabs instead of trying headless
- const result = await GetPageContent.getPageContent(
- { url: targetUrl },
- new Set([targetUrl])
- );
-
- // URL is in allowed list but not open, so should get error
- Assert.ok(
- result.includes("Cannot find URL"),
- "Should return error when tab not found"
- );
- Assert.ok(
- result.includes(targetUrl),
- "Should include requested URL in error"
- );
- } finally {
- sb.restore();
- }
-});