Bug 1333376 - Support reading the title and setting the title preface of a Window object, r?aswan draft
authorBob Silverberg <bsilverberg@mozilla.com>
Wed, 05 Jul 2017 16:53:10 -0400
changeset 609804 1e70587f2ec82f2b79ef4d17ce0ba2251bb1d02f
parent 609768 d43779e278d2e4d3e21dba2fcb585a3bf4b1288e
child 637665 6007a7ec1746f47f0e6be2a26a0d3fe57c386dc6
push id68680
push userbmo:bob.silverberg@gmail.com
push dateMon, 17 Jul 2017 14:11:05 +0000
reviewersaswan
bugs1333376
milestone56.0a1
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
browser/components/extensions/ext-utils.js
browser/components/extensions/ext-windows.js
browser/components/extensions/schemas/windows.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_windows.js
browser/components/extensions/test/browser/file_title.html
toolkit/components/extensions/ExtensionTabs.jsm
--- 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;
   }