Bug 1333376 - Support reading the title and setting the title preface of a Window object, r?aswan
Implements a title property on the Window object, and allows for a titlePreface to be set
both via windows.create and via windows.update.
MozReview-Commit-ID: CaWwPN0utzs
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -622,16 +622,24 @@ class Window extends WindowBase {
if (options.width !== null || options.height !== null) {
let width = options.width !== null ? options.width : window.outerWidth;
let height = options.height !== null ? options.height : window.outerHeight;
window.resizeTo(width, height);
}
}
+ get title() {
+ return this.window.document.title;
+ }
+
+ setTitlePreface(titlePreface) {
+ this.window.document.documentElement.setAttribute("titlepreface", titlePreface);
+ }
+
get focused() {
return this.window.document.hasFocus();
}
get top() {
return this.window.screenY;
}
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -184,16 +184,19 @@ this.windows = class extends ExtensionAP
}
if (allowScriptsToClose) {
for (let {linkedBrowser} of window.gBrowser.tabs) {
onXULFrameLoaderCreated({target: linkedBrowser});
linkedBrowser.addEventListener( // eslint-disable-line mozilla/balanced-listeners
"XULFrameLoaderCreated", onXULFrameLoaderCreated);
}
}
+ if (createData.titlePreface) {
+ win.setTitlePreface(createData.titlePreface);
+ }
return win.convert({populate: true});
});
},
update: function(windowId, updateInfo) {
if (updateInfo.state !== null && updateInfo.state != "normal") {
if (updateInfo.left !== null || updateInfo.top !== null ||
updateInfo.width !== null || updateInfo.height !== null) {
@@ -212,16 +215,21 @@ this.windows = class extends ExtensionAP
if (updateInfo.drawAttention) {
// Bug 1257497 - Firefox can't cancel attention actions.
win.window.getAttention();
}
win.updateGeometry(updateInfo);
+ if (updateInfo.titlePreface) {
+ win.setTitlePreface(updateInfo.titlePreface);
+ win.window.gBrowser.updateTitlebar();
+ }
+
// TODO: All the other properties, focused=false...
return Promise.resolve(win.convert());
},
remove: function(windowId) {
let window = windowTracker.getWindow(windowId, context);
window.close();
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -76,16 +76,21 @@
"alwaysOnTop": {
"type": "boolean",
"description": "Whether the window is set to be always on top."
},
"sessionId": {
"type": "string",
"optional": true,
"description": "The session ID used to uniquely identify a Window obtained from the $(ref:sessions) API."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "The title of the window. Read-only."
}
}
},
{
"id": "CreateType",
"type": "string",
"description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set.",
"enum": ["normal", "popup", "panel", "detached_panel"]
@@ -329,16 +334,21 @@
"$ref": "WindowState",
"optional": true,
"description": "The initial state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
},
"allowScriptsToClose": {
"type": "boolean",
"optional": true,
"description": "Allow scripts to close the window."
+ },
+ "titlePreface": {
+ "type": "string",
+ "optional": true,
+ "description": "A string to add to the beginning of the window title."
}
},
"optional": true
},
{
"type": "function",
"name": "callback",
"optional": true,
@@ -399,16 +409,21 @@
"type": "boolean",
"optional": true,
"description": "If true, causes the window to be displayed in a manner that draws the user's attention to the window, without changing the focused window. The effect lasts until the user changes focus to the window. This option has no effect if the window already has focus. Set to false to cancel a previous draw attention request."
},
"state": {
"$ref": "WindowState",
"optional": true,
"description": "The new state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
+ },
+ "titlePreface": {
+ "type": "string",
+ "optional": true,
+ "description": "A string to add to the beginning of the window title."
}
}
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": [
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -14,16 +14,17 @@ support-files =
file_popup_api_injection_b.html
file_iframe_document.html
file_iframe_document.sjs
file_bypass_cache.sjs
file_language_fr_en.html
file_language_ja.html
file_language_tlh.html
file_dummy.html
+ file_title.html
file_inspectedwindow_reload_target.sjs
file_serviceWorker.html
webNav_createdTarget.html
webNav_createdTargetSource.html
webNav_createdTargetSource_subframe.html
serviceWorker.js
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
--- a/browser/components/extensions/test/browser/browser_ext_windows.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows.js
@@ -27,16 +27,123 @@ add_task(async function testWindowGetAll
await extension.startup();
await extension.awaitFinish("alwaysOnTop");
await extension.unload();
await BrowserTestUtils.closeWindow(raisedWin);
});
+add_task(async function testWindowTitle() {
+ const PREFACE1 = "My prefix1 - ";
+ const PREFACE2 = "My prefix2 - ";
+ const START_URL = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+ const START_TITLE = "Dummy test page";
+ const NEW_URL = "http://example.com/browser/browser/components/extensions/test/browser/file_title.html";
+ const NEW_TITLE = "Different title test page";
+
+ async function background() {
+ browser.test.onMessage.addListener(async (msg, options, windowId, expected) => {
+ if (msg === "create") {
+ let win = await browser.windows.create(options);
+ browser.test.sendMessage("created", win);
+ }
+ if (msg === "update") {
+ let win = await browser.windows.get(windowId);
+ browser.test.assertTrue(win.title.startsWith(expected.before.preface),
+ "Window has the expected title preface before update.");
+ browser.test.assertTrue(win.title.includes(expected.before.text),
+ "Window has the expected title text before update.");
+ win = await browser.windows.update(windowId, options);
+ browser.test.assertTrue(win.title.startsWith(expected.after.preface),
+ "Window has the expected title preface after update.");
+ browser.test.assertTrue(win.title.includes(expected.after.text),
+ "Window has the expected title text after update.");
+ browser.test.sendMessage("updated", win);
+ }
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background,
+ });
+
+ await extension.startup();
+ let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ async function createApiWin(options) {
+ let promiseLoaded = BrowserTestUtils.waitForNewWindow(true, START_URL);
+ extension.sendMessage("create", options);
+ let apiWin = await extension.awaitMessage("created");
+ let realWin = windowTracker.getWindow(apiWin.id);
+ await promiseLoaded;
+ let expectedPreface = options.titlePreface ? options.titlePreface : "";
+ ok(realWin.document.title.startsWith(expectedPreface),
+ "Created window has the expected title preface.");
+ ok(realWin.document.title.includes(START_TITLE),
+ "Created window has the expected title text.");
+ return apiWin;
+ }
+
+ async function updateWindow(options, apiWin, expected) {
+ extension.sendMessage("update", options, apiWin.id, expected);
+ await extension.awaitMessage("updated");
+ let realWin = windowTracker.getWindow(apiWin.id);
+ ok(realWin.document.title.startsWith(expected.after.preface),
+ "Updated window has the expected title preface.");
+ ok(realWin.document.title.includes(expected.after.text),
+ "Updated window has the expected title text.");
+ await BrowserTestUtils.closeWindow(realWin);
+ }
+
+ // Create a window without a preface.
+ let apiWin = await createApiWin({url: START_URL});
+
+ // Add a titlePreface to the window.
+ let expected = {
+ before: {
+ preface: "",
+ text: START_TITLE,
+ },
+ after: {
+ preface: PREFACE1,
+ text: START_TITLE,
+ },
+ };
+ await updateWindow({titlePreface: PREFACE1}, apiWin, expected);
+
+ // Create a window with a preface.
+ apiWin = await createApiWin({url: START_URL, titlePreface: PREFACE1});
+
+ // Navigate to a different url and check that title is reflected.
+ let realWin = windowTracker.getWindow(apiWin.id);
+ let promiseLoaded = BrowserTestUtils.browserLoaded(realWin.gBrowser.selectedBrowser);
+ await BrowserTestUtils.loadURI(realWin.gBrowser.selectedBrowser, NEW_URL);
+ await promiseLoaded;
+ ok(realWin.document.title.startsWith(PREFACE1),
+ "Updated window has the expected title preface.");
+ ok(realWin.document.title.includes(NEW_TITLE),
+ "Updated window has the expected title text.");
+
+ // Update the titlePreface of the window.
+ expected = {
+ before: {
+ preface: PREFACE1,
+ text: NEW_TITLE,
+ },
+ after: {
+ preface: PREFACE2,
+ text: NEW_TITLE,
+ },
+ };
+ await updateWindow({titlePreface: PREFACE2}, apiWin, expected);
+
+ await extension.unload();
+});
+
add_task(async function testInvalidWindowId() {
let extension = ExtensionTestUtils.loadExtension({
async background() {
await browser.test.assertRejects(
// Assuming that this windowId does not exist.
browser.windows.get(123456789),
/Invalid window/,
"Should receive invalid window");
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_title.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Different title test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>A page with a different title</p>
+</body>
+</html>
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -734,16 +734,17 @@ class WindowBase {
top: this.top,
left: this.left,
width: this.width,
height: this.height,
incognito: this.incognito,
type: this.type,
state: this.state,
alwaysOnTop: this.alwaysOnTop,
+ title: this.title,
};
if (getInfo && getInfo.populate) {
result.tabs = Array.from(this.getTabs(), tab => tab.convert());
}
return result;
}