Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
firefox / usr / lib / firefox / browser / features / webcompat-reporter@mozilla.org.xpi
Size: Mime:
PK
!<³ё{33chrome.manifestlocale report-site-issue en-US en-US/locale/en-US/
PK
!<R,ͳ³
background.js/* 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/. */

"use strict";

/* globals browser */

const Config = {
  newIssueEndpoint: "https://webcompat.com/issues/new",
  newIssueEndpointPref: "newIssueEndpoint",
  screenshotFormat: {
    format: "jpeg",
    quality: 75,
  },
};

const FRAMEWORK_KEYS = ["hasFastClick", "hasMobify", "hasMarfeel"];

browser.pageActionExtras.setLabelForHistogram("webcompat");

browser.pageAction.onClicked.addListener(tab => {
  return getWebCompatInfoForTab(tab).then(
    info => {
      return openWebCompatTab(info);
    },
    err => {
      console.error("WebCompat Reporter: unexpected error", err);
    }
  );
});

browser.aboutConfigPrefs.onEndpointPrefChange.addListener(checkEndpointPref);

checkEndpointPref();

async function checkEndpointPref() {
  const value = await browser.aboutConfigPrefs.getEndpointPref();
  if (value === undefined) {
    browser.aboutConfigPrefs.setEndpointPref(Config.newIssueEndpoint);
  } else {
    Config.newIssueEndpoint = value;
  }
}

function hasFastClickPageScript() {
  const win = window.wrappedJSObject;

  if (win.FastClick) {
    return true;
  }

  for (const property in win) {
    try {
      const proto = win[property].prototype;
      if (proto && proto.needsClick) {
        return true;
      }
    } catch (_) {}
  }

  return false;
}

function hasMobifyPageScript() {
  const win = window.wrappedJSObject;
  return !!(win.Mobify && win.Mobify.Tag);
}

function hasMarfeelPageScript() {
  const win = window.wrappedJSObject;
  return !!win.marfeel;
}

function checkForFrameworks(tabId) {
  return browser.tabs
    .executeScript(tabId, {
      code: `
      (function() {
        ${hasFastClickPageScript};
        ${hasMobifyPageScript};
        ${hasMarfeelPageScript};
        
        const result = {
          hasFastClick: hasFastClickPageScript(),
          hasMobify: hasMobifyPageScript(),
          hasMarfeel: hasMarfeelPageScript(),
        }

        return result;
      })();
    `,
    })
    .then(([results]) => results)
    .catch(() => false);
}

function getWebCompatInfoForTab(tab) {
  const { id, url } = tab;
  return Promise.all([
    browser.browserInfo.getBlockList(),
    browser.browserInfo.getBuildID(),
    browser.browserInfo.getGraphicsPrefs(),
    browser.browserInfo.getUpdateChannel(),
    browser.browserInfo.hasTouchScreen(),
    browser.tabExtras.getWebcompatInfo(id),
    checkForFrameworks(id),
    browser.tabs.captureTab(id, Config.screenshotFormat).catch(e => {
      console.error("WebCompat Reporter: getting a screenshot failed", e);
      return Promise.resolve(undefined);
    }),
  ]).then(
    ([
      blockList,
      buildID,
      graphicsPrefs,
      channel,
      hasTouchScreen,
      frameInfo,
      frameworks,
      screenshot,
    ]) => {
      if (channel !== "linux") {
        delete graphicsPrefs["layers.acceleration.force-enabled"];
      }

      const consoleLog = frameInfo.log;
      delete frameInfo.log;

      return Object.assign(frameInfo, {
        tabId: id,
        blockList,
        details: Object.assign(graphicsPrefs, {
          buildID,
          channel,
          consoleLog,
          frameworks,
          hasTouchScreen,
          "mixed active content blocked":
            frameInfo.hasMixedActiveContentBlocked,
          "mixed passive content blocked":
            frameInfo.hasMixedDisplayContentBlocked,
          "tracking content blocked": frameInfo.hasTrackingContentBlocked
            ? `true (${blockList})`
            : "false",
        }),
        screenshot,
        url,
      });
    }
  );
}

function stripNonASCIIChars(str) {
  // eslint-disable-next-line no-control-regex
  return str.replace(/[^\x00-\x7F]/g, "");
}

browser.l10n
  .getMessage("wc-reporter.label2")
  .then(browser.pageActionExtras.setDefaultTitle, () => {});

browser.l10n
  .getMessage("wc-reporter.tooltip")
  .then(browser.pageActionExtras.setTooltipText, () => {});

async function openWebCompatTab(compatInfo) {
  const url = new URL(Config.newIssueEndpoint);
  const { details } = compatInfo;
  const params = {
    url: `${compatInfo.url}`,
    utm_source: "desktop-reporter",
    utm_campaign: "report-site-issue-button",
    src: "desktop-reporter",
    details,
    extra_labels: [],
  };

  for (let framework of FRAMEWORK_KEYS) {
    if (details.frameworks[framework]) {
      params.details[framework] = true;
      params.extra_labels.push(
        framework.replace(/^has/, "type-").toLowerCase()
      );
    }
  }
  delete details.frameworks;

  if (details["gfx.webrender.all"] || details["gfx.webrender.enabled"]) {
    params.extra_labels.push("type-webrender-enabled");
  }
  if (compatInfo.hasTrackingContentBlocked) {
    params.extra_labels.push(
      `type-tracking-protection-${compatInfo.blockList}`
    );
  }

  const tab = await browser.tabs.create({ url: "about:blank" });
  const json = stripNonASCIIChars(JSON.stringify(params));
  await browser.tabExtras.loadURIWithPostData(
    tab.id,
    url.href,
    json,
    "application/json"
  );
  await browser.tabs.executeScript(tab.id, {
    runAt: "document_end",
    code: `(function() {
      async function sendScreenshot(dataURI) {
        const res = await fetch(dataURI);
        const blob = await res.blob();
        postMessage(blob, "${url.origin}");
      }
      sendScreenshot("${compatInfo.screenshot}");
    })()`,
  });
}
PK
!<£ˆ¶__'en-US/locale/en-US/webcompat.properties
wc-reporter.label2=Report Site Issue…
wc-reporter.tooltip=Report a site compatibility issue
PK
!<õœô"‰‰$experimentalAPIs/aboutConfigPrefs.js/* 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/. */

"use strict";

/* global ExtensionAPI, ExtensionCommon */

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

this.aboutConfigPrefs = class extends ExtensionAPI {
  getAPI(context) {
    const EventManager = ExtensionCommon.EventManager;
    const extensionIDBase = context.extension.id.split("@")[0];
    const endpointPrefName = `extensions.${extensionIDBase}.newIssueEndpoint`;

    return {
      aboutConfigPrefs: {
        onEndpointPrefChange: new EventManager({
          context,
          name: "aboutConfigPrefs.onEndpointPrefChange",
          register: fire => {
            const callback = () => {
              fire.async().catch(() => {}); // ignore Message Manager disconnects
            };
            Services.prefs.addObserver(endpointPrefName, callback);
            return () => {
              Services.prefs.removeObserver(endpointPrefName, callback);
            };
          },
        }).api(),
        async getEndpointPref() {
          return Services.prefs.getStringPref(endpointPrefName, undefined);
        },
        async setEndpointPref(value) {
          Services.prefs.setStringPref(endpointPrefName, value);
        },
      },
    };
  }
};
PK
!<lYi‚--&experimentalAPIs/aboutConfigPrefs.json[
  {
    "namespace": "aboutConfigPrefs",
    "description": "experimental API extension to allow access to about:config preferences",
    "events": [
      {
        "name": "onEndpointPrefChange",
        "type": "function",
        "parameters": []
      }
    ],
    "functions": [
      {
        "name": "getEndpointPref",
        "type": "function",
        "description": "Get the endpoint preference's value",
        "parameters": [],
        "async": true
      },
      {
        "name": "setEndpointPref",
        "type": "function",
        "description": "Set the endpoint preference's value",
        "parameters": [
          {
            "name": "value",
            "type": "string",
            "description": "The new value"
          }
        ],
        "async": true
      }
    ]
  }
]
PK
!<!#P!experimentalAPIs/browserInfo.js/* 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/. */

"use strict";

/* global ExtensionAPI */

var { AppConstants } = ChromeUtils.import(
  "resource://gre/modules/AppConstants.jsm"
);
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

this.browserInfo = class extends ExtensionAPI {
  getAPI(context) {
    return {
      browserInfo: {
        async getGraphicsPrefs() {
          const prefs = {};
          for (const [name, dflt] of Object.entries({
            "layers.acceleration.force-enabled": false,
            "gfx.webrender.all": false,
            "gfx.webrender.blob-images": true,
            "gfx.webrender.enabled": false,
            "image.mem.shared": true,
          })) {
            prefs[name] = Services.prefs.getBoolPref(name, dflt);
          }
          return prefs;
        },
        async getAppVersion() {
          return AppConstants.MOZ_APP_VERSION;
        },
        async getBlockList() {
          const trackingTable = Services.prefs.getCharPref(
            "urlclassifier.trackingTable"
          );
          // If content-track-digest256 is in the tracking table,
          // the user has enabled the strict list.
          return trackingTable.includes("content") ? "strict" : "basic";
        },
        async getBuildID() {
          return Services.appinfo.appBuildID;
        },
        async getUpdateChannel() {
          return AppConstants.MOZ_UPDATE_CHANNEL;
        },
        async getPlatform() {
          return AppConstants.platform;
        },
        async hasTouchScreen() {
          const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(
            Ci.nsIGfxInfo
          );
          return gfxInfo.getInfo().ApzTouchInput == 1;
        },
      },
    };
  }
};
PK
!<Èf¦!experimentalAPIs/browserInfo.json[
  {
    "namespace": "browserInfo",
    "description": "experimental API extensions to get browser info not exposed via web APIs",
    "functions": [
      {
        "name": "getAppVersion",
        "type": "function",
        "description": "Gets the app version",
        "parameters": [],
        "async": true
      },
      {
        "name": "getBlockList",
        "type": "function",
        "description": "Gets the current blocklist",
        "parameters": [],
        "async": true
      },
      {
        "name": "getBuildID",
        "type": "function",
        "description": "Gets the build ID",
        "parameters": [],
        "async": true
      },
      {
        "name": "getGraphicsPrefs",
        "type": "function",
        "description": "Gets interesting about:config prefs for graphics",
        "parameters": [],
        "async": true
      },
      {
        "name": "getPlatform",
        "type": "function",
        "description": "Gets the platform",
        "parameters": [],
        "async": true
      },
      {
        "name": "getUpdateChannel",
        "type": "function",
        "description": "Gets the update channel",
        "parameters": [],
        "async": true
      },
      {
        "name": "hasTouchScreen",
        "type": "function",
        "description": "Gets whether a touchscreen is present",
        "parameters": [],
        "async": true
      }
    ]
  }
]
PK
!<uÀè|¸¸experimentalAPIs/l10n.js/* 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/. */

"use strict";

/* global ExtensionAPI, XPCOMUtils */

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyGetter(this, "l10nStrings", function() {
  return Services.strings.createBundle(
    "chrome://report-site-issue/locale/webcompat.properties"
  );
});

let l10nManifest;

this.l10n = class extends ExtensionAPI {
  onShutdown(isAppShutdown) {
    if (!isAppShutdown && l10nManifest) {
      Components.manager.removeBootstrappedManifestLocation(l10nManifest);
    }
  }
  getAPI(context) {
    // Until we move to Fluent (bug 1446164), we're stuck with
    // chrome.manifest for handling localization since its what the
    // build system can handle for localized repacks.
    if (context.extension.rootURI instanceof Ci.nsIJARURI) {
      l10nManifest = context.extension.rootURI.JARFile.QueryInterface(
        Ci.nsIFileURL
      ).file;
    } else if (context.extension.rootURI instanceof Ci.nsIFileURL) {
      l10nManifest = context.extension.rootURI.file;
    }

    if (l10nManifest) {
      Components.manager.addBootstrappedManifestLocation(l10nManifest);
    } else {
      Cu.reportError(
        "Cannot find webcompat reporter chrome.manifest for registring translated strings"
      );
    }

    return {
      l10n: {
        getMessage(name) {
          try {
            return Promise.resolve(l10nStrings.GetStringFromName(name));
          } catch (e) {
            return Promise.reject(e);
          }
        },
      },
    };
  }
};
PK
!<"²bŒÜÜexperimentalAPIs/l10n.json[
  {
    "namespace": "l10n",
    "description": "A stop-gap L10N API only meant to be used until a Fluent-based API is added in bug 1425104",
    "functions": [
      {
        "name": "getMessage",
        "type": "function",
        "description": "Gets the message with the given name",
        "parameters": [{
          "name": "name",
          "type": "string",
          "description": "The name of the message"
        }],
        "async": true
      }
    ]
  }
]
PK
!<›Ý+88$experimentalAPIs/pageActionExtras.js/* 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/. */

"use strict";

/* global ExtensionAPI */

this.pageActionExtras = class extends ExtensionAPI {
  getAPI(context) {
    const extension = context.extension;
    const pageActionAPI = extension.apiManager.getAPI(
      "pageAction",
      extension,
      context.envType
    );
    const {
      Management: {
        global: { windowTracker },
      },
    } = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
    return {
      pageActionExtras: {
        async setDefaultTitle(title) {
          pageActionAPI.defaults.title = title;
          // Make sure the new default title is considered right away
          for (const window of windowTracker.browserWindows()) {
            const tab = window.gBrowser.selectedTab;
            if (pageActionAPI.isShown(tab)) {
              pageActionAPI.updateButton(window);
            }
          }
        },
        async setLabelForHistogram(label) {
          pageActionAPI.browserPageAction._labelForHistogram = label;
        },
        async setTooltipText(text) {
          pageActionAPI.browserPageAction.setTooltip(text);
        },
      },
    };
  }
};
PK
!<Ìü“&experimentalAPIs/pageActionExtras.json[
  {
    "namespace": "pageActionExtras",
    "description": "experimental pageAction API extensions",
    "functions": [
      {
        "name": "setDefaultTitle",
        "type": "function",
        "async": true,
        "description": "Set the page action's title for all tabs",
        "parameters": [{
          "name": "title",
          "type": "string",
          "description": "title"
        }]
      },
      {
        "name": "setLabelForHistogram",
        "type": "function",
        "async": true,
        "description": "Set the page action's label for telemetry histograms",
        "parameters": [{
          "name": "label",
          "type": "string",
          "description": "label for the histogram"
        }]
      },
      {
        "name": "setTooltipText",
        "type": "function",
        "async": true,
        "description": "Set the page action's tooltip text",
        "parameters": [{
          "name": "text",
          "type": "string",
          "description": "text"
        }]
      }
    ]
  }
]
PK
!<'0=1TTexperimentalAPIs/tabExtras.js/* 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/. */

"use strict";

/* global ExtensionAPI, XPCOMUtils */

XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);

function getInfoFrameScript(messageName) {
  /* eslint-env mozilla/frame-script */

  const { Services } = ChromeUtils.import(
    "resource://gre/modules/Services.jsm"
  );

  function getInnerWindowId(window) {
    return window.windowUtils.currentInnerWindowID;
  }

  function getInnerWindowIDsForAllFrames(window) {
    const innerWindowID = getInnerWindowId(window);
    let ids = [innerWindowID];

    if (window.frames) {
      for (let i = 0; i < window.frames.length; i++) {
        ids = ids.concat(getInnerWindowIDsForAllFrames(window.frames[i]));
      }
    }

    return ids;
  }

  function getLoggedMessages(window, includePrivate = false) {
    const ids = getInnerWindowIDsForAllFrames(window);
    return getConsoleMessages(ids)
      .concat(getScriptErrors(ids, includePrivate))
      .sort((a, b) => a.timeStamp - b.timeStamp)
      .map(m => m.message);
  }

  function getConsoleMessages(windowIds) {
    const ConsoleAPIStorage = Cc[
      "@mozilla.org/consoleAPI-storage;1"
    ].getService(Ci.nsIConsoleAPIStorage);
    let messages = [];
    for (const id of windowIds) {
      messages = messages.concat(ConsoleAPIStorage.getEvents(id) || []);
    }
    return messages.map(evt => {
      const { columnNumber, filename, level, lineNumber, timeStamp } = evt;
      const args = evt.arguments
        .map(arg => {
          return "" + arg;
        })
        .join(", ");
      const message = `[console.${level}(${args}) ${filename}:${lineNumber}:${columnNumber}]`;
      return { timeStamp, message };
    });
  }

  function getScriptErrors(windowIds, includePrivate = false) {
    const messages = Services.console.getMessageArray() || [];
    return messages
      .filter(message => {
        if (message instanceof Ci.nsIScriptError) {
          if (!includePrivate && message.isFromPrivateWindow) {
            return false;
          }

          if (windowIds && !windowIds.includes(message.innerWindowID)) {
            return false;
          }

          return true;
        }

        // If this is not an nsIScriptError and we need to do window-based
        // filtering we skip this message.
        return false;
      })
      .map(error => {
        const { timeStamp, message } = error;
        return { timeStamp, message };
      });
  }

  sendAsyncMessage(messageName, {
    hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
    hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
    hasTrackingContentBlocked: docShell.hasTrackingContentBlocked,
    log: getLoggedMessages(content),
  });
}

this.tabExtras = class extends ExtensionAPI {
  getAPI(context) {
    const { tabManager } = context.extension;
    const {
      Management: {
        global: { windowTracker },
      },
    } = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);

    const { Services } = ChromeUtils.import(
      "resource://gre/modules/Services.jsm"
    );

    return {
      tabExtras: {
        async loadURIWithPostData(
          tabId,
          url,
          postDataString,
          postDataContentType
        ) {
          const tab = tabManager.get(tabId);
          if (!tab || !tab.browser) {
            return Promise.reject("Invalid tab");
          }

          try {
            new URL(url);
          } catch (_) {
            return Promise.reject("Invalid url");
          }

          if (
            typeof postDataString !== "string" &&
            !(postDataString instanceof String)
          ) {
            return Promise.reject("postDataString must be a string");
          }

          const stringStream = Cc[
            "@mozilla.org/io/string-input-stream;1"
          ].createInstance(Ci.nsIStringInputStream);
          stringStream.data = postDataString;
          const postData = Cc[
            "@mozilla.org/network/mime-input-stream;1"
          ].createInstance(Ci.nsIMIMEInputStream);
          postData.addHeader(
            "Content-Type",
            postDataContentType || "application/x-www-form-urlencoded"
          );
          postData.setData(stringStream);

          return new Promise(resolve => {
            const listener = {
              onLocationChange(
                browser,
                webProgress,
                request,
                locationURI,
                flags
              ) {
                if (
                  webProgress.isTopLevel &&
                  browser === tab.browser &&
                  locationURI.spec === url
                ) {
                  windowTracker.removeListener("progress", listener);
                  resolve();
                }
              },
            };
            windowTracker.addListener("progress", listener);

            let loadURIOptions = {
              triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
                {}
              ),
              postData,
            };
            tab.browser.webNavigation.loadURI(url, loadURIOptions);
          });
        },
        async getWebcompatInfo(tabId) {
          return new Promise(resolve => {
            const messageName = "WebExtension:GetWebcompatInfo";
            const code = `${getInfoFrameScript.toString()};getInfoFrameScript("${messageName}")`;
            const mm = tabManager.get(tabId).browser.messageManager;
            mm.loadFrameScript(`data:,${encodeURI(code)}`, false);
            mm.addMessageListener(messageName, function receiveFn(message) {
              mm.removeMessageListener(messageName, receiveFn);
              resolve(message.json);
            });
          });
        },
      },
    };
  }
};
PK
!<g`¤a¯¯experimentalAPIs/tabExtras.json[
  {
    "namespace": "tabExtras",
    "description": "experimental tab API extensions",
    "functions": [
      {
        "name": "getWebcompatInfo",
        "type": "function",
        "description": "Gets the content blocking status and script log for a given tab",
        "parameters": [{
          "type": "integer",
          "name": "tabId",
          "minimum": 0
        }],
        "async": true
      },
      {
        "name": "loadURIWithPostData",
        "type": "function",
        "description": "Loads a URI on the given tab using a POST request",
        "parameters": [{
          "type": "integer",
          "name": "tabId",
          "minimum": 0
        }, {
          "type": "string",
          "name": "url"
        }, {
          "type": "string",
          "name": "postData"
        }, {
          "type": "string",
          "name": "postDataContentType"
        }],
        "async": true
      }
    ]
  }
]
PK
!<®"•ººicons/lightbulb.svg<!-- 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" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M8 0C4.3 0 2 2.107 2 5.5c0 2.372 2.065 4.268 3 5V14c0 1.476 1.616 2 3 2s3-.524 3-2v-3.5c.935-.736 3-2.632 3-5C14 2.107 11.7 0 8 0zm1 12H7v-1h2zm-1 2a3.086 3.086 0 0 1-1-.172V13h2v.828A3.047 3.047 0 0 1 8 14zm1.445-4.832A1 1 0 0 0 9 10H7a1 1 0 0 0-.444-.831C5.845 8.691 4 7.1 4 5.5 4 2.607 6.175 2 8 2s4 .607 4 3.5c0 1.6-1.845 3.191-2.555 3.668z"/>
</svg>
PK
!<¡J­ú
manifest.json{
  "manifest_version": 2,
  "name": "WebCompat Reporter",
  "description": "Report site compatibility issues on webcompat.com",
  "author": "Thomas Wisniewski <twisniewski@mozilla.com>",
  "version": "1.1.0",
  "homepage_url": "https://github.com/mozilla/webcompat-reporter",
  "applications": {
    "gecko": {
      "id": "webcompat-reporter@mozilla.org"
    }
  },
  "experiment_apis": {
    "aboutConfigPrefs": {
      "schema": "experimentalAPIs/aboutConfigPrefs.json",
      "parent": {
        "scopes": ["addon_parent"],
        "script": "experimentalAPIs/aboutConfigPrefs.js",
        "paths": [["aboutConfigPrefs"]]
      }
    },
    "browserInfo": {
      "schema": "experimentalAPIs/browserInfo.json",
      "parent": {
        "scopes": ["addon_parent"],
        "script": "experimentalAPIs/browserInfo.js",
        "paths": [["browserInfo"]]
      }
    },
    "l10n": {
      "schema": "experimentalAPIs/l10n.json",
      "parent": {
        "scopes": ["addon_parent"],
        "script": "experimentalAPIs/l10n.js",
        "paths": [["l10n"]]
      }
    },
    "pageActionExtras": {
      "schema": "experimentalAPIs/pageActionExtras.json",
      "parent": {
        "scopes": ["addon_parent"],
        "script": "experimentalAPIs/pageActionExtras.js",
        "paths": [["pageActionExtras"]]
      }
    },
    "tabExtras": {
      "schema": "experimentalAPIs/tabExtras.json",
      "parent": {
        "scopes": ["addon_parent"],
        "script": "experimentalAPIs/tabExtras.js",
        "paths": [["tabExtras"]]
      }
    }
  },
  "icons": {
    "16": "icons/lightbulb.svg",
    "32": "icons/lightbulb.svg",
    "48": "icons/lightbulb.svg",
    "96": "icons/lightbulb.svg",
    "128": "icons/lightbulb.svg"
  },
  "permissions": [
    "tabs",
    "<all_urls>"
  ],
  "background": {
    "scripts": [
      "background.js"
    ]
  },
  "page_action": {
    "browser_style": true,
    "default_icon": "icons/lightbulb.svg",
    "default_title": "Report Site Issue…",
    "pinned": false,
    "show_matches": ["http://*/*", "https://*/*"]
  }
}
PK
!<³ё{33chrome.manifestPK
!<R,ͳ³
¤`background.jsPK
!<£ˆ¶__'>en-US/locale/en-US/webcompat.propertiesPK
!<õœô"‰‰$¤âexperimentalAPIs/aboutConfigPrefs.jsPK
!<lYi‚--&¤­experimentalAPIs/aboutConfigPrefs.jsonPK
!<!#P!¤ experimentalAPIs/browserInfo.jsPK
!<Èf¦!¤Ú'experimentalAPIs/browserInfo.jsonPK
!<uÀè|¸¸¤¨-experimentalAPIs/l10n.jsPK
!<"²bŒÜܤ–4experimentalAPIs/l10n.jsonPK
!<›Ý+88$¤ª6experimentalAPIs/pageActionExtras.jsPK
!<Ìü“&¤$<experimentalAPIs/pageActionExtras.jsonPK
!<'0=1TT¤z@experimentalAPIs/tabExtras.jsPK
!<g`¤a¯¯¤	XexperimentalAPIs/tabExtras.jsonPK
!<®"•ºº¤õ[icons/lightbulb.svgPK
!<¡J­ú
¤à^manifest.jsonPKW g