DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (dcc6d7a0dc00)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
/* 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";

this.EXPORTED_SYMBOLS = ["ExtensionManagement"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

/*
 * This file should be kept short and simple since it's loaded even
 * when no extensions are running.
 */

// Keep track of frame IDs for content windows. Mostly we can just use
// the outer window ID as the frame ID. However, the API specifies
// that top-level windows have a frame ID of 0. So we need to keep
// track of which windows are top-level. This code listens to messages
// from ExtensionContent to do that.
var Frames = {
  // Window IDs of top-level content windows.
  topWindowIds: new Set(),

  init() {
    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
      return;
    }

    Services.mm.addMessageListener("Extension:TopWindowID", this);
    Services.mm.addMessageListener("Extension:RemoveTopWindowID", this);
  },

  isTopWindowId(windowId) {
    return this.topWindowIds.has(windowId);
  },

  // Convert an outer window ID to a frame ID. An outer window ID of 0
  // is invalid.
  getId(windowId) {
    if (this.isTopWindowId(windowId)) {
      return 0;
    } else if (windowId == 0) {
      return -1;
    } else {
      return windowId;
    }
  },

  // Convert an outer window ID for a parent window to a frame
  // ID. Outer window IDs follow the same convention that
  // |window.top.parent === window.top|. The API works differently,
  // giving a frame ID of -1 for the the parent of a top-level
  // window. This function handles the conversion.
  getParentId(parentWindowId, windowId) {
    if (parentWindowId == windowId) {
      // We have a top-level window.
      return -1;
    }

    // Not a top-level window. Just return the ID as normal.
    return this.getId(parentWindowId);
  },

  receiveMessage({name, data}) {
    switch (name) {
      case "Extension:TopWindowID":
        // FIXME: Need to handle the case where the content process
        // crashes. Right now we leak its top window IDs.
        this.topWindowIds.add(data.windowId);
        break;

      case "Extension:RemoveTopWindowID":
        this.topWindowIds.delete(data.windowId);
        break;
    }
  },
};
Frames.init();

// Manage the collection of ext-*.js scripts that define the extension API.
var Scripts = {
  scripts: new Set(),

  register(script) {
    this.scripts.add(script);
  },

  getScripts() {
    return this.scripts;
  },
};

// Manage the collection of schemas/*.json schemas that define the extension API.
var Schemas = {
  schemas: new Set(),

  register(schema) {
    this.schemas.add(schema);
  },

  getSchemas() {
    return this.schemas;
  },
};

// This object manages various platform-level issues related to
// moz-extension:// URIs. It lives here so that it can be used in both
// the parent and child processes.
//
// moz-extension URIs have the form moz-extension://uuid/path. Each
// extension has its own UUID, unique to the machine it's installed
// on. This is easier and more secure than using the extension ID,
// since it makes it slightly harder to fingerprint for extensions if
// each user uses different URIs for the extension.
var Service = {
  initialized: false,

  // Map[uuid -> extension].
  // extension can be an Extension (parent process) or BrowserExtensionContent (child process).
  uuidMap: new Map(),

  init() {
    let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService);
    aps = aps.wrappedJSObject;
    this.aps = aps;
    aps.setExtensionURILoadCallback(this.extensionURILoadableByAnyone.bind(this));
    aps.setExtensionURIToAddonIdCallback(this.extensionURIToAddonID.bind(this));
  },

  // Called when a new extension is loaded.
  startupExtension(uuid, uri, extension) {
    if (!this.initialized) {
      this.initialized = true;
      this.init();
    }

    // Create the moz-extension://uuid mapping.
    let handler = Services.io.getProtocolHandler("moz-extension");
    handler.QueryInterface(Ci.nsISubstitutingProtocolHandler);
    handler.setSubstitution(uuid, uri);

    this.uuidMap.set(uuid, extension);
    this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension));
    this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension));
  },

  // Called when an extension is unloaded.
  shutdownExtension(uuid) {
    let extension = this.uuidMap.get(uuid);
    this.uuidMap.delete(uuid);
    this.aps.setAddonLoadURICallback(extension.id, null);
    this.aps.setAddonLocalizeCallback(extension.id, null);

    let handler = Services.io.getProtocolHandler("moz-extension");
    handler.QueryInterface(Ci.nsISubstitutingProtocolHandler);
    handler.setSubstitution(uuid, null);
  },

  // Return true if the given URI can be loaded from arbitrary web
  // content. The manifest.json |web_accessible_resources| directive
  // determines this.
  extensionURILoadableByAnyone(uri) {
    let uuid = uri.host;
    let extension = this.uuidMap.get(uuid);
    if (!extension) {
      return false;
    }

    let path = uri.path;
    if (path.length > 0 && path[0] == "/") {
      path = path.substr(1);
    }
    return extension.webAccessibleResources.has(path);
  },

  // Checks whether a given extension can load this URI (typically via
  // an XML HTTP request). The manifest.json |permissions| directive
  // determines this.
  checkAddonMayLoad(extension, uri) {
    return extension.whiteListedHosts.matchesIgnoringPath(uri);
  },

  // Finds the add-on ID associated with a given moz-extension:// URI.
  // This is used to set the addonId on the originAttributes for the
  // nsIPrincipal attached to the URI.
  extensionURIToAddonID(uri) {
    if (this.extensionURILoadableByAnyone(uri)) {
      // We don't want webAccessibleResources to be associated with
      // the add-on. That way they don't get any special privileges.
      return null;
    }

    let uuid = uri.host;
    let extension = this.uuidMap.get(uuid);
    return extension ? extension.id : undefined;
  },
};

this.ExtensionManagement = {
  startupExtension: Service.startupExtension.bind(Service),
  shutdownExtension: Service.shutdownExtension.bind(Service),

  registerScript: Scripts.register.bind(Scripts),
  getScripts: Scripts.getScripts.bind(Scripts),

  registerSchema: Schemas.register.bind(Schemas),
  getSchemas: Schemas.getSchemas.bind(Schemas),

  getFrameId: Frames.getId.bind(Frames),
  getParentFrameId: Frames.getParentId.bind(Frames),
};