Bug 1468667 - [webext] Add missing pieces to allow WebExtensions to run without error
This is largely copied from browser/components/extensions/ and modified where appropriate.
MozReview-Commit-ID: 47mw7ILAwjz
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/extensions-messenger.manifest
@@ -0,0 +1,1 @@
+category webextension-scripts messenger chrome://messenger/content/parent/ext-messenger.js
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/parent/ext-messenger.js (parent/ext-messenger.js)
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_COMPONENTS += [
+ 'extensions-messenger.manifest',
+]
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/parent/ext-messenger.js
@@ -0,0 +1,211 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let tabTracker;
+let windowTracker;
+
+global.openOptionsPage = (extension) => {
+ let window = windowTracker.topWindow;
+ if (!window) {
+ return Promise.reject({message: "No browser window available"});
+ }
+
+ if (extension.manifest.options_ui.open_in_tab) {
+ window.openContentTab(extension.manifest.options_ui.page);
+ return Promise.resolve();
+ }
+
+ let viewId = `addons://detail/${encodeURIComponent(extension.id)}/preferences`;
+
+ window.openAddonsMgr(viewId);
+ return Promise.resolve();
+};
+
+global.makeWidgetId = id => {
+ id = id.toLowerCase();
+ // FIXME: This allows for collisions.
+ return id.replace(/[^a-z0-9_-]/g, "_");
+};
+
+class WindowTracker extends WindowTrackerBase {
+ /* Override WindowTrackerBase, which looks for navigator:browser windows instead */
+ get topWindow() {
+ return Services.wm.getMostRecentWindow("mail:3pane");
+ }
+
+ isBrowserWindow(window) {
+ let {documentElement} = window.document;
+
+ return documentElement.getAttribute("windowtype") === "mail:3pane";
+ }
+}
+
+windowTracker = new WindowTracker();
+tabTracker = {
+ getBrowserData: function(target) {
+ return {};
+ }
+};
+
+Object.assign(global, {tabTracker, windowTracker});
+
+class Window extends WindowBase {
+ /**
+ * Update the geometry of the browser window.
+ *
+ * @param {Object} options
+ * An object containing new values for the window's geometry.
+ * @param {integer} [options.left]
+ * The new pixel distance of the left side of the browser window from
+ * the left of the screen.
+ * @param {integer} [options.top]
+ * The new pixel distance of the top side of the browser window from
+ * the top of the screen.
+ * @param {integer} [options.width]
+ * The new pixel width of the window.
+ * @param {integer} [options.height]
+ * The new pixel height of the window.
+ */
+ updateGeometry(options) {
+ let {window} = this;
+
+ if (options.left !== null || options.top !== null) {
+ let left = options.left !== null ? options.left : window.screenX;
+ let top = options.top !== null ? options.top : window.screenY;
+ window.moveTo(left, top);
+ }
+
+ 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;
+ }
+
+ get left() {
+ return this.window.screenX;
+ }
+
+ get width() {
+ return this.window.outerWidth;
+ }
+
+ get height() {
+ return this.window.outerHeight;
+ }
+
+ get incognito() {
+ return PrivateBrowsingUtils.isWindowPrivate(this.window);
+ }
+
+ get alwaysOnTop() {
+ return this.xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ;
+ }
+
+ get isLastFocused() {
+ return this.window === windowTracker.topWindow;
+ }
+
+ static getState(window) {
+ const STATES = {
+ [window.STATE_MAXIMIZED]: "maximized",
+ [window.STATE_MINIMIZED]: "minimized",
+ [window.STATE_NORMAL]: "normal",
+ };
+ let state = STATES[window.windowState];
+ if (window.fullScreen) {
+ state = "fullscreen";
+ }
+ return state;
+ }
+
+ get state() {
+ return Window.getState(this.window);
+ }
+
+ set state(state) {
+ let {window} = this;
+ if (state !== "fullscreen" && window.fullScreen) {
+ window.fullScreen = false;
+ }
+
+ switch (state) {
+ case "maximized":
+ window.maximize();
+ break;
+
+ case "minimized":
+ case "docked":
+ window.minimize();
+ break;
+
+ case "normal":
+ // Restore sometimes returns the window to its previous state, rather
+ // than to the "normal" state, so it may need to be called anywhere from
+ // zero to two times.
+ window.restore();
+ if (window.windowState !== window.STATE_NORMAL) {
+ window.restore();
+ }
+ if (window.windowState !== window.STATE_NORMAL) {
+ // And on OS-X, where normal vs. maximized is basically a heuristic,
+ // we need to cheat.
+ window.sizeToContent();
+ }
+ break;
+
+ case "fullscreen":
+ window.fullScreen = true;
+ break;
+
+ default:
+ throw new Error(`Unexpected window state: ${state}`);
+ }
+ }
+
+ get activeTab() {
+ return null;
+ }
+}
+
+Object.assign(global, {Window});
+
+class WindowManager extends WindowManagerBase {
+ get(windowId, context) {
+ let window = windowTracker.getWindow(windowId, context);
+
+ return this.getWrapper(window);
+ }
+
+ * getAll() {
+ for (let window of windowTracker.browserWindows()) {
+ yield this.getWrapper(window);
+ }
+ }
+
+ wrapWindow(window) {
+ return new Window(this.extension, window, windowTracker.getId(window));
+ }
+}
+
+extensions.on("startup", (type, extension) => { // eslint-disable-line mozilla/balanced-listeners
+ defineLazyGetter(extension, "windowManager",
+ () => new WindowManager(extension));
+});
--- a/mail/components/moz.build
+++ b/mail/components/moz.build
@@ -15,16 +15,17 @@ DIRS += [
'migration',
'activity',
'search',
'about-support',
'accountcreation',
'wintaskbar',
'newmailaccount',
'im',
+ 'extensions',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk3', 'cocoa'):
DIRS += ['shell']
TEST_DIRS += ['test']
DIRS += ['build']
--- a/mail/installer/package-manifest.in
+++ b/mail/installer/package-manifest.in
@@ -570,16 +570,17 @@
@RESPATH@/contentaccessible/*
; svg
@RESPATH@/res/svg.css
; [Extensions]
@RESPATH@/components/extensions-toolkit.manifest
@RESPATH@/components/extension-process-script.js
+@RESPATH@/components/extensions-messenger.manifest
; [Personal Security Manager]
;
; NSS libraries are signed in the staging directory,
; meaning their .chk files are created there directly.
;
#ifndef MOZ_SYSTEM_NSS
#if defined(XP_LINUX) && !defined(ANDROID)