--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -220,16 +220,22 @@ var CustomizableUIInternal = {
let showCharacterEncoding = Services.prefs.getComplexValue(
"browser.menu.showCharacterEncoding",
Ci.nsIPrefLocalizedString
).data;
if (showCharacterEncoding == "true") {
panelPlacements.push("characterencoding-button");
}
+ if (AppConstants.NIGHTLY_BUILD) {
+ if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
+ panelPlacements.push("webcompat-reporter-button");
+ }
+ }
+
this.registerArea(CustomizableUI.AREA_PANEL, {
anchor: "PanelUI-menu-button",
type: CustomizableUI.TYPE_MENU_PANEL,
defaultPlacements: panelPlacements
}, true);
PanelWideWidgetTracker.init();
let navbarPlacements = [
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -21,8 +21,14 @@ if not CONFIG['RELEASE_OR_BETA']:
'presentation',
]
# Only include mortar system add-ons if we locally enable it
if CONFIG['MOZ_MORTAR']:
DIRS += [
'mortar',
]
+
+# Nightly-only system add-ons
+if CONFIG['NIGHTLY_BUILD']:
+ DIRS += [
+ 'webcompat-reporter',
+ ]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/bootstrap.js
@@ -0,0 +1,46 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const WEBCOMPATREPORTER_JSM = "chrome://webcompat-reporter/content/WebCompatReporter.jsm";
+
+XPCOMUtils.defineLazyModuleGetter(this, "WebCompatReporter",
+ WEBCOMPATREPORTER_JSM);
+
+const PREF_WC_REPORTER_ENABLED = "extensions.webcompat-reporter.enabled";
+
+let prefObserver = function(aSubject, aTopic, aData) {
+ let enabled = Services.prefs.getBoolPref(PREF_WC_REPORTER_ENABLED);
+ if (enabled) {
+ WebCompatReporter.init();
+ } else {
+ WebCompatReporter.uninit();
+ }
+};
+
+function startup(aData, aReason) {
+ // Observe pref changes and enable/disable as necessary.
+ Services.prefs.addObserver(PREF_WC_REPORTER_ENABLED, prefObserver, false);
+
+ // Only initialize if pref is enabled.
+ let enabled = Services.prefs.getBoolPref(PREF_WC_REPORTER_ENABLED);
+ if (enabled) {
+ WebCompatReporter.init();
+ }
+}
+
+function shutdown(aData, aReason) {
+ if (aReason === APP_SHUTDOWN) {
+ return;
+ }
+
+ Cu.unload(WEBCOMPATREPORTER_JSM);
+}
+
+function install(aData, aReason) {}
+function uninstall(aData, aReason) {}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/content/TabListener.jsm
@@ -0,0 +1,63 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["TabListener"];
+
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+
+const WIDGET_ID = "webcompat-reporter-button";
+
+// Class that watches for url/location/tab changes and enables or disables
+// the Report Site Issue button accordingly
+class TabListener {
+ constructor(win) {
+ this.win = win;
+ this.browser = win.gBrowser;
+ this.addListeners();
+ }
+
+ addListeners() {
+ this.browser.addTabsProgressListener(this);
+ this.browser.tabContainer.addEventListener("TabSelect", this);
+ }
+
+ removeListeners() {
+ this.browser.removeTabsProgressListener(this);
+ this.browser.tabContainer.removeEventListener("TabSelect", this);
+ }
+
+ handleEvent(e) {
+ switch (e.type) {
+ case "TabSelect":
+ this.setButtonState(e.target.linkedBrowser.currentURI.scheme);
+ break;
+ }
+ }
+
+ onLocationChange(browser, webProgress, request, uri, flags) {
+ this.setButtonState(uri.scheme);
+ }
+
+ static isReportableScheme(scheme) {
+ return ["http", "https"].some((prefix) => scheme.startsWith(prefix));
+ }
+
+ setButtonState(scheme) {
+ // Bail early if the button is in the palette.
+ if (!CustomizableUI.getPlacementOfWidget(WIDGET_ID)) {
+ return;
+ }
+
+ if (TabListener.isReportableScheme(scheme)) {
+ CustomizableUI.getWidget(WIDGET_ID).forWindow(this.win).node.removeAttribute("disabled");
+ } else {
+ CustomizableUI.getWidget(WIDGET_ID).forWindow(this.win).node.setAttribute("disabled", true);
+ }
+ }
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/content/WebCompatReporter.jsm
@@ -0,0 +1,163 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["WebCompatReporter"];
+
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "wcStrings", function() {
+ return Services.strings.createBundle(
+ "chrome://webcompat-reporter/locale/webcompat.properties");
+});
+
+XPCOMUtils.defineLazyGetter(this, "wcStyleURI", function() {
+ return Services.io.newURI("chrome://webcompat-reporter/skin/lightbulb.css");
+});
+
+const WIDGET_ID = "webcompat-reporter-button";
+const TABLISTENER_JSM = "chrome://webcompat-reporter/content/TabListener.jsm";
+
+let WebCompatReporter = {
+ get endpoint() {
+ return Services.urlFormatter.formatURLPref(
+ "extensions.webcompat-reporter.newIssueEndpoint");
+ },
+
+ init() {
+ Cu.import(TABLISTENER_JSM);
+
+ let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(Ci.nsIStyleSheetService);
+ this._sheetType = styleSheetService.AUTHOR_SHEET;
+ this._cachedSheet = styleSheetService.preloadSheet(wcStyleURI,
+ this._sheetType);
+
+ CustomizableUI.createWidget({
+ id: WIDGET_ID,
+ label: wcStrings.GetStringFromName("wc-reporter.label"),
+ tooltiptext: wcStrings.GetStringFromName("wc-reporter.tooltip"),
+ defaultArea: CustomizableUI.AREA_PANEL,
+ disabled: true,
+ onCommand: (e) => this.reportIssue(e.target.ownerDocument),
+ });
+
+ for (let win of CustomizableUI.windows) {
+ this.onWindowOpened(win);
+ }
+
+ CustomizableUI.addListener(this);
+ },
+
+ onWindowOpened(win) {
+ // Attach stylesheet for the button icon.
+ win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .addSheet(this._cachedSheet, this._sheetType);
+ // Attach listeners to new window.
+ win._webcompatReporterTabListener = new TabListener(win);
+ },
+
+ onWindowClosed(win) {
+ if (win._webcompatReporterTabListener) {
+ win._webcompatReporterTabListener.removeListeners();
+ delete win._webcompatReporterTabListener;
+ }
+ },
+
+ uninit() {
+ CustomizableUI.destroyWidget(WIDGET_ID);
+
+ for (let win of CustomizableUI.windows) {
+ this.onWindowClosed(win);
+
+ win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .removeSheet(wcStyleURI, this._sheetType);
+ }
+
+ CustomizableUI.removeListener(this);
+ Cu.unload(TABLISTENER_JSM);
+ },
+
+ // This method injects a framescript that should send back a screenshot blob
+ // of the top-level window of the currently selected tab, resolved as a
+ // Promise.
+ getScreenshot(gBrowser) {
+ const FRAMESCRIPT = "chrome://webcompat-reporter/content/tab-frame.js";
+ const TABDATA_MESSAGE = "WebCompat:SendTabData";
+
+ return new Promise((resolve) => {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.loadFrameScript(FRAMESCRIPT, false);
+
+ mm.addMessageListener(TABDATA_MESSAGE, function receiveFn(message) {
+ mm.removeMessageListener(TABDATA_MESSAGE, receiveFn);
+ resolve([gBrowser, message.json]);
+ });
+ });
+ },
+
+ // This should work like so:
+ // 1) set up listeners for a new webcompat.com tab, and open it, passing
+ // along the current URI
+ // 2) if we successfully got a screenshot from getScreenshot,
+ // inject a frame script that will postMessage it to webcompat.com
+ // so it can show a preview to the user and include it in FormData
+ // Note: openWebCompatTab arguments are passed in as an array because they
+ // are the result of a promise resolution.
+ openWebCompatTab([gBrowser, tabData]) {
+ const SCREENSHOT_MESSAGE = "WebCompat:SendScreenshot";
+ const FRAMESCRIPT = "chrome://webcompat-reporter/content/wc-frame.js";
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ const WEBCOMPAT_ORIGIN = new win.URL(WebCompatReporter.endpoint).origin;
+
+ let tab = gBrowser.loadOneTab(
+ `${WebCompatReporter.endpoint}?url=${encodeURIComponent(tabData.url)}&src=desktop-reporter`,
+ {inBackground: false});
+
+ // If we successfully got a screenshot blob, add a listener to know when
+ // the new tab is loaded before sending it over.
+ if (tabData && tabData.blob) {
+ let browser = gBrowser.getBrowserForTab(tab);
+ let loadedListener = {
+ QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener",
+ "nsISupportsWeakReference"]),
+ onStateChange(webProgress, request, flags, status) {
+ let isStopped = flags & Ci.nsIWebProgressListener.STATE_STOP;
+ let isNetwork = flags & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+ if (isStopped && isNetwork && webProgress.isTopLevel) {
+ let location;
+ try {
+ location = request.QueryInterface(Ci.nsIChannel).URI;
+ } catch (ex) {}
+
+ if (location && location.prePath === WEBCOMPAT_ORIGIN) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.loadFrameScript(FRAMESCRIPT, false);
+ mm.sendAsyncMessage(SCREENSHOT_MESSAGE, {
+ screenshot: tabData.blob,
+ origin: WEBCOMPAT_ORIGIN
+ });
+
+ browser.removeProgressListener(this);
+ }
+ }
+ }
+ };
+
+ browser.addProgressListener(loadedListener);
+ }
+ },
+
+ reportIssue(xulDoc) {
+ this.getScreenshot(xulDoc.defaultView.gBrowser).then(this.openWebCompatTab)
+ .catch(Cu.reportError);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/content/tab-frame.js
@@ -0,0 +1,37 @@
+/* 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 { utils: Cu } = Components;
+
+const TABDATA_MESSAGE = "WebCompat:SendTabData";
+
+let getScreenshot = function(win) {
+ return new Promise(resolve => {
+ let url = win.location.href;
+ try {
+ let dpr = win.devicePixelRatio;
+ let canvas = win.document.createElement("canvas");
+ let ctx = canvas.getContext("2d");
+ let x = win.document.documentElement.scrollLeft;
+ let y = win.document.documentElement.scrollTop;
+ let w = win.innerWidth;
+ let h = win.innerHeight;
+ canvas.width = dpr * w;
+ canvas.height = dpr * h;
+ ctx.scale(dpr, dpr);
+ ctx.drawWindow(win, x, y, w, h, "#fff");
+ canvas.toBlob(blob => {
+ resolve({url, blob});
+ });
+ } catch (ex) {
+ // CanvasRenderingContext2D.drawWindow can fail depending on memory or
+ // surface size. Rather than reject, resolve the URL so the user can
+ // file an issue without a screenshot.
+ Cu.reportError(`WebCompatReporter: getting a screenshot failed: ${ex}`);
+ resolve({url});
+ }
+ });
+};
+
+getScreenshot(content).then(data => sendAsyncMessage(TABDATA_MESSAGE, data));
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/content/wc-frame.js
@@ -0,0 +1,23 @@
+/* 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 { utils: Cu } = Components;
+
+const SCREENSHOT_MESSAGE = "WebCompat:SendScreenshot";
+
+addMessageListener(SCREENSHOT_MESSAGE, function handleMessage(message) {
+ removeMessageListener(SCREENSHOT_MESSAGE, handleMessage);
+ // postMessage the screenshot blob from a content Sandbox so message event.origin
+ // is what we expect on the client-side (i.e., https://webcompat.com)
+ try {
+ let sb = new Cu.Sandbox(content.document.nodePrincipal);
+ sb.win = content;
+ sb.screenshotBlob = Cu.cloneInto(message.data.screenshot, content);
+ sb.wcOrigin = Cu.cloneInto(message.data.origin, content);
+ Cu.evalInSandbox("win.postMessage(screenshotBlob, wcOrigin);", sb);
+ Cu.nukeSandbox(sb);
+ } catch (ex) {
+ Cu.reportError(`WebCompatReporter: sending a screenshot failed: ${ex}`);
+ }
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/install.rdf.in
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>webcompat-reporter@mozilla.org</em:id>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <em:name>WebCompat Reporter</em:name>
+ <em:description>Report site compatibility issues on webcompat.com.</em:description>
+
+ <em:version>1.0.0</em:version>
+
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/jar.mn
@@ -0,0 +1,9 @@
+# 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/.
+
+[features/webcompat-reporter@mozilla.org] chrome.jar:
+% content webcompat-reporter %content/
+% skin webcompat-reporter classic/1.0 %skin/
+ content/ (content/*)
+ skin/ (skin/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/locale/en-US/webcompat.properties
@@ -0,0 +1,2 @@
+wc-reporter.label=Report Site Issue
+wc-reporter.tooltip=Report a site compatibility issue
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/locale/jar.mn
@@ -0,0 +1,8 @@
+#filter substitution
+# 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/.
+
+[features/webcompat-reporter@mozilla.org] @AB_CD@.jar:
+% locale webcompat-reporter @AB_CD@ %locale/@AB_CD@/
+ locale/@AB_CD@/ (en-US/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/locale/moz.build
@@ -0,0 +1,7 @@
+# -*- 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']
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/moz.build
@@ -0,0 +1,22 @@
+# -*- 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/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+DIRS += ['locale']
+
+FINAL_TARGET_FILES.features['webcompat-reporter@mozilla.org'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['webcompat-reporter@mozilla.org'] += [
+ 'install.rdf.in'
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/skin/lightbulb.css
@@ -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/. */
+#webcompat-reporter-button {
+ list-style-image: url("chrome://webcompat-reporter/skin/lightbulb.svg");
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/skin/lightbulb.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 85 128">
+ <path d="M85.23,34.52v-7L43.34,35.21v6.94Zm0-16L43.34,26.28v6.94L85.23,25.5Zm-2.86-1.47v-.26A15,15,0,0,0,81.58,12h0a1.23,1.23,0,0,1-.17-.52,3.62,3.62,0,0,0-.35-.87.3.3,0,0,0-.09-.17h0A18.33,18.33,0,0,0,64.15,0C55.91,0,49,5,46.72,12h25.5L45.94,16.82l-2.6.43v7l4.08-.78Z" transform="translate(-23.46)" fill-opacity="0.6"/>
+ <path d="M28.65,92.41c1.7,18,14.52,30.59,35.83,30.59s34.13-12.61,35.83-30.59c1.33-18.21-4.72-23.88-12.75-35.61a36.8,36.8,0,0,1-5.23-11.57s-24.25.29-24.33,0H46.71A36.8,36.8,0,0,1,41.47,56.8C33.36,68.52,27.32,74.2,28.65,92.41Z" transform="translate(-23.46)" fill="none" stroke="#000" stroke-opacity="0.6" stroke-width="10"/>
+ <path d="M82.11,86.19c-.17-.26-.35-.43-.61-2L70.83,51.58c-1.13.17-.69.17-1.13.17h-.78c-.52.17-.35.78-.17,1.3L78.81,83.67a11.18,11.18,0,0,0-8-1,7.69,7.69,0,0,0-1.47.69l-4.25-1.3a8.07,8.07,0,0,0-4.94,1.65,11.54,11.54,0,0,0-2.6-1,10.73,10.73,0,0,0-7.46.52L60,53.14a3.83,3.83,0,0,0,.26-1.39c-2.25-.09-2.25.09-2.17.69L47.24,85.58a1.2,1.2,0,0,0-.26.35,1,1,0,1,0,1.56,1.21c2-2.52,4.86-3.3,8.5-2.34a5.55,5.55,0,0,1,1.65.61,5.19,5.19,0,0,0-.87,3.12c.09,2.25.87,3.82,2.08,4.42a2.16,2.16,0,0,0,2.17-.09c1-.69,1.56-2.34,1.39-4.42a5.24,5.24,0,0,0-1.65-3.38,6.35,6.35,0,0,1,6-.17,6.65,6.65,0,0,0-1.13,3.56c-.09,2,.52,3.56,1.65,4.25a2.48,2.48,0,0,0,2.34.09c1.13-.69,1.82-2.25,1.73-4.25A5.78,5.78,0,0,0,71,84.88c.17-.09.26-.09.43-.17,3-1,7.46.78,9.11,2.78a1,1,0,0,0,1.39.17A1.09,1.09,0,0,0,82.11,86.19ZM60.86,91.13h-.09a3.58,3.58,0,0,1-1-2.69,4,4,0,0,1,.52-2,3,3,0,0,1,1,2.08C61.55,90.09,61.2,91,60.86,91.13ZM69.62,91h-.09a.27.27,0,0,1-.17-.09c-.26-.17-.78-.87-.69-2.43A5.31,5.31,0,0,1,69.36,86a3.51,3.51,0,0,1,1,2.34C70.31,90.09,69.88,90.78,69.62,91Z" transform="translate(-23.46)" fill="none" stroke="#000" stroke-opacity="0.6" stroke-width="5"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head.js
+ test.html
+ webcompat.html
+
+[browser_disabled_cleanup.js]
+[browser_button_state.js]
+[browser_report_site_issue.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/browser_button_state.js
@@ -0,0 +1,28 @@
+const REPORTABLE_PAGE = "http://example.com/";
+const REPORTABLE_PAGE2 = "https://example.com/";
+const NONREPORTABLE_PAGE = "about:blank";
+
+/* Test that the Report Site Issue button is enabled for http and https tabs,
+ on page load, or TabSelect, and disabled for everything else. */
+add_task(function* test_button_state_disabled() {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, REPORTABLE_PAGE);
+ yield PanelUI.show();
+ is(isButtonDisabled(), false, "Check that button is enabled for reportable schemes on tab load");
+
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, NONREPORTABLE_PAGE);
+ is(isButtonDisabled(), true, "Check that button is disabled for non-reportable schemes on tab load");
+
+ let tab3 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, REPORTABLE_PAGE2);
+ is(isButtonDisabled(), false, "Check that button is enabled for reportable schemes on tab load");
+
+
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ is(isButtonDisabled(), true, "Check that button is disabled for non-reportable schemes on TabSelect");
+
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ is(isButtonDisabled(), false, "Check that button is enabled for reportable schemes on TabSelect");
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab3);
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/browser_disabled_cleanup.js
@@ -0,0 +1,9 @@
+// Test the addon is cleaning up after itself when disabled.
+add_task(function* test_disabled() {
+ yield SpecialPowers.pushPrefEnv({set: [[PREF_WC_REPORTER_ENABLED, false]]});
+
+ yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function() {
+ is(typeof window._webCompatReporterTabListener, "undefined", "TabListener expando does not exist.");
+ is(document.getElementById("webcompat-reporter-button"), null, "Report Site Issue button does not exist.");
+ });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/browser_report_site_issue.js
@@ -0,0 +1,32 @@
+/* Test that clicking on the Report Site Issue button opens a new tab
+ and sends a postMessaged blob at it.
+ testing/profiles/prefs_general.js sets the value for
+ "extensions.webcompat-reporter.newIssueEndpoint" */
+add_task(function* test_screenshot() {
+ yield SpecialPowers.pushPrefEnv({set: [[PREF_WC_REPORTER_ENDPOINT, NEW_ISSUE_PAGE]]});
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
+ yield PanelUI.show();
+
+ let webcompatButton = document.getElementById("webcompat-reporter-button");
+ ok(webcompatButton, "Report Site Issue button exists.");
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ webcompatButton.click();
+ let tab2 = yield newTabPromise;
+
+ yield BrowserTestUtils.waitForContentEvent(tab2.linkedBrowser, "ScreenshotReceived", false, null, true);
+
+ yield ContentTask.spawn(tab2.linkedBrowser, {TEST_PAGE}, function(args) {
+ let doc = content.document;
+ let urlParam = doc.getElementById("url").innerText;
+ let preview = doc.getElementById("screenshot-preview");
+ is(urlParam, args.TEST_PAGE, "Reported page is correctly added to the url param");
+
+ is(preview.innerText, "Pass", "A Blob object was successfully transferred to the test page.")
+ ok(preview.style.backgroundImage.startsWith("url(\"data:image/png;base64,iVBOR"), "A green screenshot was successfully postMessaged");
+ });
+
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/head.js
@@ -0,0 +1,10 @@
+const PREF_WC_REPORTER_ENABLED = "extensions.webcompat-reporter.enabled";
+const PREF_WC_REPORTER_ENDPOINT = "extensions.webcompat-reporter.newIssueEndpoint";
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+const TEST_PAGE = TEST_ROOT + "test.html";
+const NEW_ISSUE_PAGE = TEST_ROOT + "webcompat.html";
+
+function isButtonDisabled() {
+ return document.getElementById("webcompat-reporter-button").disabled;
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/test.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ body {background: rgb(0, 128, 0);}
+</style>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat-reporter/test/browser/webcompat.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #screenshot-preview {width: 200px; height: 200px;}
+</style>
+<div id="url"></div>
+<div id="screenshot-preview">Fail</div>
+<script>
+let params = new URL(location.href).searchParams;
+let preview = document.getElementById("screenshot-preview");
+let url = document.getElementById("url");
+url.innerText = params.get("url");
+
+function getBlobAsDataURL(blob) {
+ return new Promise((resolve, reject) => {
+ let reader = new FileReader();
+
+ reader.addEventListener("error", (e) => {
+ reject(`There was an error reading the blob: ${e.type}`);
+ });
+
+ reader.addEventListener("load", (e) => {
+ resolve(e.target.result);
+ });
+
+ reader.readAsDataURL(blob);
+ });
+}
+
+function setPreviewBG(backgroundData) {
+ return new Promise((resolve) => {
+ preview.style.background = `url(${backgroundData})`;
+ resolve();
+ });
+}
+
+function sendReceivedEvent() {
+ window.dispatchEvent(new CustomEvent("ScreenshotReceived", {bubbles:true}));
+}
+
+window.addEventListener("message", function(event) {
+ if (event.data instanceof Blob) {
+ preview.innerText = "Pass";
+ }
+
+ getBlobAsDataURL(event.data).then(setPreviewBG).then(sendReceivedEvent);
+});
+</script>
\ No newline at end of file
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -101,16 +101,19 @@ libs-%:
ifndef RELEASE_OR_BETA
@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
endif
@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
+ifdef NIGHTLY_BUILD
+ @$(MAKE) -C ../extensions/webcompat-reporter/locale AB_CD=$* XPI_NAME=locale-$*
+endif
repackage-win32-installer: WIN32_INSTALLER_OUT=$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
@echo 'Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT).'
$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
$(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
$(MAKE) repackage-zip \
AB_CD=$(AB_CD) \
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -6,16 +6,18 @@
this.EXPORTED_SYMBOLS = ["BrowserUITelemetry"];
const {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
"resource://gre/modules/UITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
"resource:///modules/UITour.jsm");
@@ -74,16 +76,22 @@ XPCOMUtils.defineLazyGetter(this, "DEFAU
let showCharacterEncoding = Services.prefs.getComplexValue(
"browser.menu.showCharacterEncoding",
Ci.nsIPrefLocalizedString
).data;
if (showCharacterEncoding == "true") {
result["PanelUI-contents"].push("characterencoding-button");
}
+ if (AppConstants.NIGHTLY_BUILD) {
+ if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
+ result["PanelUI-contents"].push("webcompat-reporter-button");
+ }
+ }
+
return result;
});
XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREAS", function() {
return Object.keys(DEFAULT_AREA_PLACEMENTS);
});
XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4740,16 +4740,24 @@ pref("extensions.webExtensionsMinPlatfor
// Other webextensions prefs
pref("extensions.webextensions.keepStorageOnUninstall", false);
pref("extensions.webextensions.keepUuidOnUninstall", false);
// Redirect basedomain used by identity api
pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
pref("extensions.webextensions.remote", false);
+// Report Site Issue button
+pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
+#ifdef NIGHTLY_BUILD
+pref("extensions.webcompat-reporter.enabled", true);
+#else
+pref("extensions.webcompat-reporter.enabled", false);
+#endif
+
pref("network.buffer.cache.count", 24);
pref("network.buffer.cache.size", 32768);
// Desktop Notification
pref("notification.feature.enabled", false);
// Web Notification
pref("dom.webnotifications.enabled", true);