browser/base/content/browser-plugins.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/base/content/browser-plugins.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1332 @@
     1.4 +# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.8 +
     1.9 +var gPluginHandler = {
    1.10 +  PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash",
    1.11 +  PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification",
    1.12 +  PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
    1.13 +  PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
    1.14 +
    1.15 +  getPluginUI: function (plugin, anonid) {
    1.16 +    return plugin.ownerDocument.
    1.17 +           getAnonymousElementByAttribute(plugin, "anonid", anonid);
    1.18 +  },
    1.19 +
    1.20 +#ifdef MOZ_CRASHREPORTER
    1.21 +  get CrashSubmit() {
    1.22 +    delete this.CrashSubmit;
    1.23 +    Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
    1.24 +    return this.CrashSubmit;
    1.25 +  },
    1.26 +#endif
    1.27 +
    1.28 +  _getPluginInfo: function (pluginElement) {
    1.29 +    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
    1.30 +    pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
    1.31 +
    1.32 +    let tagMimetype;
    1.33 +    let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin");
    1.34 +    let pluginTag = null;
    1.35 +    let permissionString = null;
    1.36 +    let fallbackType = null;
    1.37 +    let blocklistState = null;
    1.38 +
    1.39 +    tagMimetype = pluginElement.actualType;
    1.40 +    if (tagMimetype == "") {
    1.41 +      tagMimetype = pluginElement.type;
    1.42 +    }
    1.43 +
    1.44 +    if (gPluginHandler.isKnownPlugin(pluginElement)) {
    1.45 +      pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
    1.46 +      pluginName = gPluginHandler.makeNicePluginName(pluginTag.name);
    1.47 +
    1.48 +      permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
    1.49 +      fallbackType = pluginElement.defaultFallbackType;
    1.50 +      blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
    1.51 +      // Make state-softblocked == state-notblocked for our purposes,
    1.52 +      // they have the same UI. STATE_OUTDATED should not exist for plugin
    1.53 +      // items, but let's alias it anyway, just in case.
    1.54 +      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
    1.55 +          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
    1.56 +        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
    1.57 +      }
    1.58 +    }
    1.59 +
    1.60 +    return { mimetype: tagMimetype,
    1.61 +             pluginName: pluginName,
    1.62 +             pluginTag: pluginTag,
    1.63 +             permissionString: permissionString,
    1.64 +             fallbackType: fallbackType,
    1.65 +             blocklistState: blocklistState,
    1.66 +           };
    1.67 +  },
    1.68 +
    1.69 +  // Map the plugin's name to a filtered version more suitable for user UI.
    1.70 +  makeNicePluginName : function (aName) {
    1.71 +    if (aName == "Shockwave Flash")
    1.72 +      return "Adobe Flash";
    1.73 +
    1.74 +    // Clean up the plugin name by stripping off parenthetical clauses,
    1.75 +    // trailing version numbers or "plugin".
    1.76 +    // EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar"
    1.77 +    // Do this by first stripping the numbers, etc. off the end, and then
    1.78 +    // removing "Plugin" (and then trimming to get rid of any whitespace).
    1.79 +    // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
    1.80 +    let newName = aName.replace(/\(.*?\)/g, "").
    1.81 +                        replace(/[\s\d\.\-\_\(\)]+$/, "").
    1.82 +                        replace(/\bplug-?in\b/i, "").trim();
    1.83 +    return newName;
    1.84 +  },
    1.85 +
    1.86 +  /**
    1.87 +   * Update the visibility of the plugin overlay.
    1.88 +   */
    1.89 +  setVisibility : function (plugin, overlay, shouldShow) {
    1.90 +    overlay.classList.toggle("visible", shouldShow);
    1.91 +  },
    1.92 +
    1.93 +  /**
    1.94 +   * Check whether the plugin should be visible on the page. A plugin should
    1.95 +   * not be visible if the overlay is too big, or if any other page content
    1.96 +   * overlays it.
    1.97 +   *
    1.98 +   * This function will handle showing or hiding the overlay.
    1.99 +   * @returns true if the plugin is invisible.
   1.100 +   */
   1.101 +  shouldShowOverlay : function (plugin, overlay) {
   1.102 +    // If the overlay size is 0, we haven't done layout yet. Presume that
   1.103 +    // plugins are visible until we know otherwise.
   1.104 +    if (overlay.scrollWidth == 0) {
   1.105 +      return true;
   1.106 +    }
   1.107 +
   1.108 +    // Is the <object>'s size too small to hold what we want to show?
   1.109 +    let pluginRect = plugin.getBoundingClientRect();
   1.110 +    // XXX bug 446693. The text-shadow on the submitted-report text at
   1.111 +    //     the bottom causes scrollHeight to be larger than it should be.
   1.112 +    let overflows = (overlay.scrollWidth > pluginRect.width) ||
   1.113 +                    (overlay.scrollHeight - 5 > pluginRect.height);
   1.114 +    if (overflows) {
   1.115 +      return false;
   1.116 +    }
   1.117 +
   1.118 +    // Is the plugin covered up by other content so that it is not clickable?
   1.119 +    // Floating point can confuse .elementFromPoint, so inset just a bit
   1.120 +    let left = pluginRect.left + 2;
   1.121 +    let right = pluginRect.right - 2;
   1.122 +    let top = pluginRect.top + 2;
   1.123 +    let bottom = pluginRect.bottom - 2;
   1.124 +    let centerX = left + (right - left) / 2;
   1.125 +    let centerY = top + (bottom - top) / 2;
   1.126 +    let points = [[left, top],
   1.127 +                   [left, bottom],
   1.128 +                   [right, top],
   1.129 +                   [right, bottom],
   1.130 +                   [centerX, centerY]];
   1.131 +
   1.132 +    if (right <= 0 || top <= 0) {
   1.133 +      return false;
   1.134 +    }
   1.135 +
   1.136 +    let contentWindow = plugin.ownerDocument.defaultView;
   1.137 +    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   1.138 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.139 +
   1.140 +    for (let [x, y] of points) {
   1.141 +      let el = cwu.elementFromPoint(x, y, true, true);
   1.142 +      if (el !== plugin) {
   1.143 +        return false;
   1.144 +      }
   1.145 +    }
   1.146 +
   1.147 +    return true;
   1.148 +  },
   1.149 +
   1.150 +  addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
   1.151 +    // XXX just doing (callback)(arg) was giving a same-origin error. bug?
   1.152 +    let self = this;
   1.153 +    let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
   1.154 +    linkNode.addEventListener("click",
   1.155 +                              function(evt) {
   1.156 +                                if (!evt.isTrusted)
   1.157 +                                  return;
   1.158 +                                evt.preventDefault();
   1.159 +                                if (callbackArgs.length == 0)
   1.160 +                                  callbackArgs = [ evt ];
   1.161 +                                (self[callbackName]).apply(self, callbackArgs);
   1.162 +                              },
   1.163 +                              true);
   1.164 +
   1.165 +    linkNode.addEventListener("keydown",
   1.166 +                              function(evt) {
   1.167 +                                if (!evt.isTrusted)
   1.168 +                                  return;
   1.169 +                                if (evt.keyCode == evt.DOM_VK_RETURN) {
   1.170 +                                  evt.preventDefault();
   1.171 +                                  if (callbackArgs.length == 0)
   1.172 +                                    callbackArgs = [ evt ];
   1.173 +                                  evt.preventDefault();
   1.174 +                                  (self[callbackName]).apply(self, callbackArgs);
   1.175 +                                }
   1.176 +                              },
   1.177 +                              true);
   1.178 +  },
   1.179 +
   1.180 +  // Helper to get the binding handler type from a plugin object
   1.181 +  _getBindingType : function(plugin) {
   1.182 +    if (!(plugin instanceof Ci.nsIObjectLoadingContent))
   1.183 +      return null;
   1.184 +
   1.185 +    switch (plugin.pluginFallbackType) {
   1.186 +      case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
   1.187 +        return "PluginNotFound";
   1.188 +      case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
   1.189 +        return "PluginDisabled";
   1.190 +      case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
   1.191 +        return "PluginBlocklisted";
   1.192 +      case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
   1.193 +        return "PluginOutdated";
   1.194 +      case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
   1.195 +        return "PluginClickToPlay";
   1.196 +      case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
   1.197 +        return "PluginVulnerableUpdatable";
   1.198 +      case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
   1.199 +        return "PluginVulnerableNoUpdate";
   1.200 +      case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
   1.201 +        return "PluginPlayPreview";
   1.202 +      default:
   1.203 +        // Not all states map to a handler
   1.204 +        return null;
   1.205 +    }
   1.206 +  },
   1.207 +
   1.208 +  supportedPlugins: {
   1.209 +    "mimetypes": {
   1.210 +      "application/x-shockwave-flash": "flash",
   1.211 +      "application/futuresplash": "flash",
   1.212 +      "application/x-java-.*": "java",
   1.213 +      "application/x-director": "shockwave",
   1.214 +      "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime",
   1.215 +      "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime",
   1.216 +      "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime",
   1.217 +      "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime",
   1.218 +      "application/x-unknown": "test",
   1.219 +    },
   1.220 +
   1.221 +    "plugins": {
   1.222 +      "flash": {
   1.223 +        "displayName": "Flash",
   1.224 +        "installWINNT": true,
   1.225 +        "installDarwin": true,
   1.226 +        "installLinux": true,
   1.227 +      },
   1.228 +      "java": {
   1.229 +        "displayName": "Java",
   1.230 +        "installWINNT": true,
   1.231 +        "installDarwin": true,
   1.232 +        "installLinux": true,
   1.233 +      },
   1.234 +      "shockwave": {
   1.235 +        "displayName": "Shockwave",
   1.236 +        "installWINNT": true,
   1.237 +        "installDarwin": true,
   1.238 +      },
   1.239 +      "quicktime": {
   1.240 +        "displayName": "QuickTime",
   1.241 +        "installWINNT": true,
   1.242 +      },
   1.243 +      "test": {
   1.244 +        "displayName": "Test plugin",
   1.245 +        "installWINNT": true,
   1.246 +        "installLinux": true,
   1.247 +        "installDarwin": true,
   1.248 +      }
   1.249 +    }
   1.250 +  },
   1.251 +
   1.252 +  nameForSupportedPlugin: function (aMimeType) {
   1.253 +    for (let type in this.supportedPlugins.mimetypes) {
   1.254 +      let re = new RegExp(type);
   1.255 +      if (re.test(aMimeType)) {
   1.256 +        return this.supportedPlugins.mimetypes[type];
   1.257 +      }
   1.258 +    }
   1.259 +    return null;
   1.260 +  },
   1.261 +
   1.262 +  canInstallThisMimeType: function (aMimeType) {
   1.263 +    let os = Services.appinfo.OS;
   1.264 +    let pluginName = this.nameForSupportedPlugin(aMimeType);
   1.265 +    if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) {
   1.266 +      return true;
   1.267 +    }
   1.268 +    return false;
   1.269 +  },
   1.270 +
   1.271 +  handleEvent : function(event) {
   1.272 +    let eventType = event.type;
   1.273 +
   1.274 +    if (eventType == "PluginRemoved") {
   1.275 +      let doc = event.target;
   1.276 +      let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
   1.277 +      if (browser)
   1.278 +        this._setPluginNotificationIcon(browser);
   1.279 +      return;
   1.280 +    }
   1.281 +
   1.282 +    let plugin = event.target;
   1.283 +    let doc = plugin.ownerDocument;
   1.284 +
   1.285 +    if (!(plugin instanceof Ci.nsIObjectLoadingContent))
   1.286 +      return;
   1.287 +
   1.288 +    if (eventType == "PluginBindingAttached") {
   1.289 +      // The plugin binding fires this event when it is created.
   1.290 +      // As an untrusted event, ensure that this object actually has a binding
   1.291 +      // and make sure we don't handle it twice
   1.292 +      let overlay = this.getPluginUI(plugin, "main");
   1.293 +      if (!overlay || overlay._bindingHandled) {
   1.294 +        return;
   1.295 +      }
   1.296 +      overlay._bindingHandled = true;
   1.297 +
   1.298 +      // Lookup the handler for this binding
   1.299 +      eventType = this._getBindingType(plugin);
   1.300 +      if (!eventType) {
   1.301 +        // Not all bindings have handlers
   1.302 +        return;
   1.303 +      }
   1.304 +    }
   1.305 +
   1.306 +    let shouldShowNotification = false;
   1.307 +    let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
   1.308 +    if (!browser)
   1.309 +      return;
   1.310 +
   1.311 +    switch (eventType) {
   1.312 +      case "PluginCrashed":
   1.313 +        this.pluginInstanceCrashed(plugin, event);
   1.314 +        break;
   1.315 +
   1.316 +      case "PluginNotFound":
   1.317 +        let installable = this.showInstallNotification(plugin, eventType);
   1.318 +        // For non-object plugin tags, register a click handler to install the
   1.319 +        // plugin. Object tags can, and often do, deal with that themselves,
   1.320 +        // so don't stomp on the page developers toes.
   1.321 +        if (installable && !(plugin instanceof HTMLObjectElement)) {
   1.322 +          let installStatus = this.getPluginUI(plugin, "installStatus");
   1.323 +          installStatus.setAttribute("installable", "true");
   1.324 +          let iconStatus = this.getPluginUI(plugin, "icon");
   1.325 +          iconStatus.setAttribute("installable", "true");
   1.326 +
   1.327 +          let installLink = this.getPluginUI(plugin, "installPluginLink");
   1.328 +          this.addLinkClickCallback(installLink, "installSinglePlugin", plugin);
   1.329 +        }
   1.330 +        break;
   1.331 +
   1.332 +      case "PluginBlocklisted":
   1.333 +      case "PluginOutdated":
   1.334 +        shouldShowNotification = true;
   1.335 +        break;
   1.336 +
   1.337 +      case "PluginVulnerableUpdatable":
   1.338 +        let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
   1.339 +        this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
   1.340 +        /* FALLTHRU */
   1.341 +
   1.342 +      case "PluginVulnerableNoUpdate":
   1.343 +      case "PluginClickToPlay":
   1.344 +        this._handleClickToPlayEvent(plugin);
   1.345 +        let overlay = this.getPluginUI(plugin, "main");
   1.346 +        let pluginName = this._getPluginInfo(plugin).pluginName;
   1.347 +        let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
   1.348 +        let overlayText = this.getPluginUI(plugin, "clickToPlay");
   1.349 +        overlayText.textContent = messageString;
   1.350 +        if (eventType == "PluginVulnerableUpdatable" ||
   1.351 +            eventType == "PluginVulnerableNoUpdate") {
   1.352 +          let vulnerabilityString = gNavigatorBundle.getString(eventType);
   1.353 +          let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
   1.354 +          vulnerabilityText.textContent = vulnerabilityString;
   1.355 +        }
   1.356 +        shouldShowNotification = true;
   1.357 +        break;
   1.358 +
   1.359 +      case "PluginPlayPreview":
   1.360 +        this._handlePlayPreviewEvent(plugin);
   1.361 +        break;
   1.362 +
   1.363 +      case "PluginDisabled":
   1.364 +        // Screw the disabled message. It messes with HTML5 fallback on YouTube
   1.365 +        let plugin_overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
   1.366 +        if (plugin_overlay != null)
   1.367 +          plugin_overlay.style.visibility = "hidden";
   1.368 +        break;
   1.369 +
   1.370 +      case "PluginInstantiated":
   1.371 +        shouldShowNotification = true;
   1.372 +        break;
   1.373 +    }
   1.374 +
   1.375 +    // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
   1.376 +    if (eventType != "PluginCrashed") {
   1.377 +      let overlay = this.getPluginUI(plugin, "main");
   1.378 +      if (overlay != null) {
   1.379 +        this.setVisibility(plugin, overlay,
   1.380 +                           this.shouldShowOverlay(plugin, overlay));
   1.381 +        let resizeListener = (event) => {
   1.382 +          this.setVisibility(plugin, overlay,
   1.383 +            this.shouldShowOverlay(plugin, overlay));
   1.384 +          this._setPluginNotificationIcon(browser);
   1.385 +        };
   1.386 +        plugin.addEventListener("overflow", resizeListener);
   1.387 +        plugin.addEventListener("underflow", resizeListener);
   1.388 +      }
   1.389 +    }
   1.390 +
   1.391 +    let closeIcon = this.getPluginUI(plugin, "closeIcon");
   1.392 +    if (closeIcon) {
   1.393 +      closeIcon.addEventListener("click", function(aEvent) {
   1.394 +        if (aEvent.button == 0 && aEvent.isTrusted)
   1.395 +          gPluginHandler.hideClickToPlayOverlay(plugin);
   1.396 +      }, true);
   1.397 +    }
   1.398 +
   1.399 +    if (shouldShowNotification) {
   1.400 +      this._showClickToPlayNotification(browser, plugin, false);
   1.401 +    }
   1.402 +  },
   1.403 +
   1.404 +  isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
   1.405 +    return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
   1.406 +            Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
   1.407 +  },
   1.408 +
   1.409 +  canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
   1.410 +    // if this isn't a known plugin, we can't activate it
   1.411 +    // (this also guards pluginHost.getPermissionStringForType against
   1.412 +    // unexpected input)
   1.413 +    if (!gPluginHandler.isKnownPlugin(objLoadingContent))
   1.414 +      return false;
   1.415 +
   1.416 +    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   1.417 +    let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
   1.418 +    let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal;
   1.419 +    let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
   1.420 +
   1.421 +    let isFallbackTypeValid =
   1.422 +      objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
   1.423 +      objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
   1.424 +
   1.425 +    if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) {
   1.426 +      // checking if play preview is subject to CTP rules
   1.427 +      let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType);
   1.428 +      isFallbackTypeValid = !playPreviewInfo.ignoreCTP;
   1.429 +    }
   1.430 +
   1.431 +    return !objLoadingContent.activated &&
   1.432 +           pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
   1.433 +           isFallbackTypeValid;
   1.434 +  },
   1.435 +
   1.436 +  hideClickToPlayOverlay: function(aPlugin) {
   1.437 +    let overlay = this.getPluginUI(aPlugin, "main");
   1.438 +    if (overlay) {
   1.439 +      overlay.classList.remove("visible");
   1.440 +    }
   1.441 +  },
   1.442 +
   1.443 +  stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
   1.444 +    let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
   1.445 +    if (objLoadingContent.activated)
   1.446 +      return;
   1.447 +
   1.448 +    if (aPlayPlugin)
   1.449 +      objLoadingContent.playPlugin();
   1.450 +    else
   1.451 +      objLoadingContent.cancelPlayPreview();
   1.452 +  },
   1.453 +
   1.454 +  newPluginInstalled : function(event) {
   1.455 +    // browser elements are anonymous so we can't just use target.
   1.456 +    var browser = event.originalTarget;
   1.457 +    // clear the plugin list, now that at least one plugin has been installed
   1.458 +    browser.missingPlugins = null;
   1.459 +
   1.460 +    var notificationBox = gBrowser.getNotificationBox(browser);
   1.461 +    var notification = notificationBox.getNotificationWithValue("missing-plugins");
   1.462 +    if (notification)
   1.463 +      notificationBox.removeNotification(notification);
   1.464 +
   1.465 +    // reload the browser to make the new plugin show.
   1.466 +    browser.reload();
   1.467 +  },
   1.468 +
   1.469 +  // Callback for user clicking on a missing (unsupported) plugin.
   1.470 +  installSinglePlugin: function (plugin) {
   1.471 +    var missingPlugins = new Map();
   1.472 +
   1.473 +    var pluginInfo = this._getPluginInfo(plugin);
   1.474 +    missingPlugins.set(pluginInfo.mimetype, pluginInfo);
   1.475 +
   1.476 +    openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
   1.477 +               "PFSWindow", "chrome,centerscreen,resizable=yes",
   1.478 +               {plugins: missingPlugins, browser: gBrowser.selectedBrowser});
   1.479 +  },
   1.480 +
   1.481 +  // Callback for user clicking on a disabled plugin
   1.482 +  managePlugins: function (aEvent) {
   1.483 +    BrowserOpenAddonsMgr("addons://list/plugin");
   1.484 +  },
   1.485 +
   1.486 +  // Callback for user clicking on the link in a click-to-play plugin
   1.487 +  // (where the plugin has an update)
   1.488 +  openPluginUpdatePage: function (aEvent) {
   1.489 +    openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab");
   1.490 +  },
   1.491 +
   1.492 +#ifdef MOZ_CRASHREPORTER
   1.493 +  submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
   1.494 +    let keyVals = {};
   1.495 +    if (plugin) {
   1.496 +      let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
   1.497 +      if (userComment)
   1.498 +        keyVals.PluginUserComment = userComment;
   1.499 +      if (this.getPluginUI(plugin, "submitURLOptIn").checked)
   1.500 +        keyVals.PluginContentURL = plugin.ownerDocument.URL;
   1.501 +    }
   1.502 +    this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals });
   1.503 +    if (browserDumpID)
   1.504 +      this.CrashSubmit.submit(browserDumpID);
   1.505 +  },
   1.506 +#endif
   1.507 +
   1.508 +  // Callback for user clicking a "reload page" link
   1.509 +  reloadPage: function (browser) {
   1.510 +    browser.reload();
   1.511 +  },
   1.512 +
   1.513 +  // Callback for user clicking the help icon
   1.514 +  openHelpPage: function () {
   1.515 +    openHelpLink("plugin-crashed", false);
   1.516 +  },
   1.517 +
   1.518 +  showInstallNotification: function (aPlugin) {
   1.519 +    let hideMissingPluginsNotification =
   1.520 +      Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION);
   1.521 +    if (hideMissingPluginsNotification) {
   1.522 +      return false;
   1.523 +    }
   1.524 +
   1.525 +    let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument
   1.526 +                                                        .defaultView.top.document);
   1.527 +    if (!browser.missingPlugins)
   1.528 +      browser.missingPlugins = new Map();
   1.529 +
   1.530 +    let pluginInfo = this._getPluginInfo(aPlugin);
   1.531 +    browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo);
   1.532 +
   1.533 +    // only show notification for small subset of plugins
   1.534 +    let mimetype = pluginInfo.mimetype.split(";")[0];
   1.535 +    if (!this.canInstallThisMimeType(mimetype))
   1.536 +      return false;
   1.537 +
   1.538 +    let pluginIdentifier = this.nameForSupportedPlugin(mimetype);
   1.539 +    if (!pluginIdentifier)
   1.540 +      return false;
   1.541 +
   1.542 +    let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName;
   1.543 +
   1.544 +    // don't show several notifications
   1.545 +    let notification = PopupNotifications.getNotification("plugins-not-found", browser);
   1.546 +    if (notification)
   1.547 +      return true;
   1.548 +
   1.549 +    let messageString = gNavigatorBundle.getString("installPlugin.message");
   1.550 +    let mainAction = {
   1.551 +      label: gNavigatorBundle.getFormattedString("installPlugin.button.label",
   1.552 +                                                 [displayName]),
   1.553 +      accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"),
   1.554 +      callback: function () {
   1.555 +        openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
   1.556 +                   "PFSWindow", "chrome,centerscreen,resizable=yes",
   1.557 +                   {plugins: browser.missingPlugins, browser: browser});
   1.558 +      }
   1.559 +    };
   1.560 +    let secondaryActions = null;
   1.561 +    let options = { dismissed: true };
   1.562 +
   1.563 +    let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH);
   1.564 +    if (pluginIdentifier == "flash" && showForFlash) {
   1.565 +      let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH;
   1.566 +      secondaryActions = [{
   1.567 +        label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"),
   1.568 +        accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"),
   1.569 +        callback: function () {
   1.570 +          Services.prefs.setBoolPref(prefNotifyMissingFlash, false);
   1.571 +        }
   1.572 +      }];
   1.573 +      options.dismissed = false;
   1.574 +    }
   1.575 +    PopupNotifications.show(browser, "plugins-not-found",
   1.576 +                            messageString, "plugin-install-notification-icon",
   1.577 +                            mainAction, secondaryActions, options);
   1.578 +    return true;
   1.579 +  },
   1.580 +  // Event listener for click-to-play plugins.
   1.581 +  _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
   1.582 +    let doc = aPlugin.ownerDocument;
   1.583 +    let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
   1.584 +    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   1.585 +    let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
   1.586 +    // guard against giving pluginHost.getPermissionStringForType a type
   1.587 +    // not associated with any known plugin
   1.588 +    if (!gPluginHandler.isKnownPlugin(objLoadingContent))
   1.589 +      return;
   1.590 +    let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
   1.591 +    let principal = doc.defaultView.top.document.nodePrincipal;
   1.592 +    let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
   1.593 +
   1.594 +    let overlay = this.getPluginUI(aPlugin, "main");
   1.595 +
   1.596 +    if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
   1.597 +      if (overlay) {
   1.598 +        overlay.classList.remove("visible");
   1.599 +      }
   1.600 +      return;
   1.601 +    }
   1.602 +
   1.603 +    if (overlay) {
   1.604 +      overlay.addEventListener("click", gPluginHandler._overlayClickListener, true);
   1.605 +    }
   1.606 +  },
   1.607 +
   1.608 +  _overlayClickListener: {
   1.609 +    handleEvent: function PH_handleOverlayClick(aEvent) {
   1.610 +      let plugin = document.getBindingParent(aEvent.target);
   1.611 +      let contentWindow = plugin.ownerDocument.defaultView.top;
   1.612 +      // gBrowser.getBrowserForDocument does not exist in the case where we
   1.613 +      // drag-and-dropped a tab from a window containing only that tab. In
   1.614 +      // that case, the window gets destroyed.
   1.615 +      let browser = gBrowser.getBrowserForDocument ?
   1.616 +                      gBrowser.getBrowserForDocument(contentWindow.document) :
   1.617 +                      null;
   1.618 +      // If browser is null here, we've been drag-and-dropped from another
   1.619 +      // window, and this is the wrong click handler.
   1.620 +      if (!browser) {
   1.621 +        aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true);
   1.622 +        return;
   1.623 +      }
   1.624 +      let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   1.625 +      // Have to check that the target is not the link to update the plugin
   1.626 +      if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
   1.627 +          (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
   1.628 +          aEvent.button == 0 && aEvent.isTrusted) {
   1.629 +        gPluginHandler._showClickToPlayNotification(browser, plugin, true);
   1.630 +        aEvent.stopPropagation();
   1.631 +        aEvent.preventDefault();
   1.632 +      }
   1.633 +    }
   1.634 +  },
   1.635 +
   1.636 +  _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
   1.637 +    let doc = aPlugin.ownerDocument;
   1.638 +    let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
   1.639 +    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   1.640 +    let pluginInfo = this._getPluginInfo(aPlugin);
   1.641 +    let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype);
   1.642 +
   1.643 +    let previewContent = this.getPluginUI(aPlugin, "previewPluginContent");
   1.644 +    let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
   1.645 +    if (!iframe) {
   1.646 +      // lazy initialization of the iframe
   1.647 +      iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
   1.648 +      iframe.className = "previewPluginContentFrame";
   1.649 +      previewContent.appendChild(iframe);
   1.650 +
   1.651 +      // Force a style flush, so that we ensure our binding is attached.
   1.652 +      aPlugin.clientTop;
   1.653 +    }
   1.654 +    iframe.src = playPreviewInfo.redirectURL;
   1.655 +
   1.656 +    // MozPlayPlugin event can be dispatched from the extension chrome
   1.657 +    // code to replace the preview content with the native plugin
   1.658 +    previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
   1.659 +      if (!aEvent.isTrusted)
   1.660 +        return;
   1.661 +
   1.662 +      previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
   1.663 +
   1.664 +      let playPlugin = !aEvent.detail;
   1.665 +      gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
   1.666 +
   1.667 +      // cleaning up: removes overlay iframe from the DOM
   1.668 +      let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
   1.669 +      if (iframe)
   1.670 +        previewContent.removeChild(iframe);
   1.671 +    }, true);
   1.672 +
   1.673 +    if (!playPreviewInfo.ignoreCTP) {
   1.674 +      gPluginHandler._showClickToPlayNotification(browser, aPlugin, false);
   1.675 +    }
   1.676 +  },
   1.677 +
   1.678 +  reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
   1.679 +    let browser = gBrowser.selectedBrowser;
   1.680 +    let contentWindow = browser.contentWindow;
   1.681 +    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   1.682 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.683 +    let plugins = cwu.plugins;
   1.684 +    for (let plugin of plugins) {
   1.685 +      let overlay = this.getPluginUI(plugin, "main");
   1.686 +      if (overlay)
   1.687 +        overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
   1.688 +      let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   1.689 +      if (gPluginHandler.canActivatePlugin(objLoadingContent))
   1.690 +        gPluginHandler._handleClickToPlayEvent(plugin);
   1.691 +    }
   1.692 +    gPluginHandler._showClickToPlayNotification(browser, null, false);
   1.693 +  },
   1.694 +
   1.695 +  _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
   1.696 +    if (event == "showing") {
   1.697 +      Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
   1.698 +        .add(!this.options.primaryPlugin);
   1.699 +      // Histograms always start at 0, even though our data starts at 1
   1.700 +      let histogramCount = this.options.pluginData.size - 1;
   1.701 +      if (histogramCount > 4) {
   1.702 +        histogramCount = 4;
   1.703 +      }
   1.704 +      Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
   1.705 +        .add(histogramCount);
   1.706 +    }
   1.707 +    else if (event == "dismissed") {
   1.708 +      // Once the popup is dismissed, clicking the icon should show the full
   1.709 +      // list again
   1.710 +      this.options.primaryPlugin = null;
   1.711 +    }
   1.712 +  },
   1.713 +
   1.714 +  // Match the behaviour of nsPermissionManager
   1.715 +  _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) {
   1.716 +    if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) {
   1.717 +      return "(null)";
   1.718 +    }
   1.719 +
   1.720 +    try {
   1.721 +      if (principal.URI.host)
   1.722 +        return principal.URI.host;
   1.723 +    } catch (e) {}
   1.724 +
   1.725 +    return principal.origin;
   1.726 +  },
   1.727 +
   1.728 +  /**
   1.729 +   * Called from the plugin doorhanger to set the new permissions for a plugin
   1.730 +   * and activate plugins if necessary.
   1.731 +   * aNewState should be either "allownow" "allowalways" or "block"
   1.732 +   */
   1.733 +  _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) {
   1.734 +    let permission;
   1.735 +    let expireType;
   1.736 +    let expireTime;
   1.737 +    let histogram =
   1.738 +      Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
   1.739 +
   1.740 +    // Update the permission manager.
   1.741 +    // Also update the current state of pluginInfo.fallbackType so that
   1.742 +    // subsequent opening of the notification shows the current state.
   1.743 +    switch (aNewState) {
   1.744 +      case "allownow":
   1.745 +        permission = Ci.nsIPermissionManager.ALLOW_ACTION;
   1.746 +        expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
   1.747 +        expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
   1.748 +        histogram.add(0);
   1.749 +        aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
   1.750 +        break;
   1.751 +
   1.752 +      case "allowalways":
   1.753 +        permission = Ci.nsIPermissionManager.ALLOW_ACTION;
   1.754 +        expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
   1.755 +        expireTime = Date.now() +
   1.756 +          Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
   1.757 +        histogram.add(1);
   1.758 +        aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
   1.759 +        break;
   1.760 +
   1.761 +      case "block":
   1.762 +        permission = Ci.nsIPermissionManager.PROMPT_ACTION;
   1.763 +        expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
   1.764 +        expireTime = 0;
   1.765 +        histogram.add(2);
   1.766 +        switch (aPluginInfo.blocklistState) {
   1.767 +          case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
   1.768 +            aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
   1.769 +            break;
   1.770 +          case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
   1.771 +            aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
   1.772 +            break;
   1.773 +          default:
   1.774 +            aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
   1.775 +        }
   1.776 +        break;
   1.777 +
   1.778 +      // In case a plugin has already been allowed in another tab, the "continue allowing" button
   1.779 +      // shouldn't change any permissions but should run the plugin-enablement code below.
   1.780 +      case "continue":
   1.781 +        aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
   1.782 +        break;
   1.783 +      default:
   1.784 +        Cu.reportError(Error("Unexpected plugin state: " + aNewState));
   1.785 +        return;
   1.786 +    }
   1.787 +
   1.788 +    let browser = aNotification.browser;
   1.789 +    let contentWindow = browser.contentWindow;
   1.790 +    if (aNewState != "continue") {
   1.791 +      let principal = contentWindow.document.nodePrincipal;
   1.792 +      Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
   1.793 +                                      permission, expireType, expireTime);
   1.794 +      aPluginInfo.pluginPermissionType = expireType;
   1.795 +    }
   1.796 +
   1.797 +    // Manually activate the plugins that would have been automatically
   1.798 +    // activated.
   1.799 +    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   1.800 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.801 +    let plugins = cwu.plugins;
   1.802 +    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   1.803 +
   1.804 +    let pluginFound = false;
   1.805 +    for (let plugin of plugins) {
   1.806 +      plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   1.807 +      if (!gPluginHandler.isKnownPlugin(plugin)) {
   1.808 +        continue;
   1.809 +      }
   1.810 +      if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
   1.811 +        pluginFound = true;
   1.812 +        if (aNewState == "block") {
   1.813 +          plugin.reload(true);
   1.814 +        } else {
   1.815 +          if (gPluginHandler.canActivatePlugin(plugin)) {
   1.816 +            let overlay = this.getPluginUI(plugin, "main");
   1.817 +            if (overlay) {
   1.818 +              overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
   1.819 +            }
   1.820 +            plugin.playPlugin();
   1.821 +          }
   1.822 +        }
   1.823 +      }
   1.824 +    }
   1.825 +
   1.826 +    // If there are no instances of the plugin on the page any more, what the
   1.827 +    // user probably needs is for us to allow and then refresh.
   1.828 +    if (aNewState != "block" && !pluginFound) {
   1.829 +      browser.reload();
   1.830 +    }
   1.831 +
   1.832 +    this._setPluginNotificationIcon(browser);
   1.833 +  },
   1.834 +
   1.835 +  _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) {
   1.836 +    let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
   1.837 +    let plugins = [];
   1.838 +
   1.839 +    // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need
   1.840 +    // to collect all the plugins
   1.841 +    if (aPlugin === null) {
   1.842 +      let contentWindow = aBrowser.contentWindow;
   1.843 +      let contentDoc = aBrowser.contentDocument;
   1.844 +      let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   1.845 +                             .getInterface(Ci.nsIDOMWindowUtils);
   1.846 +      // cwu.plugins may contain non-plugin <object>s, filter them out
   1.847 +      plugins = cwu.plugins.filter((plugin) =>
   1.848 +        plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
   1.849 +
   1.850 +      if (plugins.length == 0) {
   1.851 +        if (notification) {
   1.852 +          PopupNotifications.remove(notification);
   1.853 +        }
   1.854 +        return;
   1.855 +      }
   1.856 +    } else {
   1.857 +      plugins = [aPlugin];
   1.858 +    }
   1.859 +
   1.860 +    // If this is a new notification, create a pluginData map, otherwise append
   1.861 +    let pluginData;
   1.862 +    if (notification) {
   1.863 +      pluginData = notification.options.pluginData;
   1.864 +    } else {
   1.865 +      pluginData = new Map();
   1.866 +    }
   1.867 +
   1.868 +    let principal = aBrowser.contentDocument.nodePrincipal;
   1.869 +    let principalHost = this._getHostFromPrincipal(principal);
   1.870 +
   1.871 +    for (var plugin of plugins) {
   1.872 +      let pluginInfo = this._getPluginInfo(plugin);
   1.873 +      if (pluginInfo.permissionString === null) {
   1.874 +        Cu.reportError("No permission string for active plugin.");
   1.875 +        continue;
   1.876 +      }
   1.877 +      if (pluginData.has(pluginInfo.permissionString)) {
   1.878 +        continue;
   1.879 +      }
   1.880 +
   1.881 +      let permissionObj = Services.perms.
   1.882 +        getPermissionObject(principal, pluginInfo.permissionString, false);
   1.883 +      if (permissionObj) {
   1.884 +        pluginInfo.pluginPermissionHost = permissionObj.host;
   1.885 +        pluginInfo.pluginPermissionType = permissionObj.expireType;
   1.886 +      }
   1.887 +      else {
   1.888 +        pluginInfo.pluginPermissionHost = principalHost;
   1.889 +        pluginInfo.pluginPermissionType = undefined;
   1.890 +      }
   1.891 +
   1.892 +      let url;
   1.893 +      // TODO: allow the blocklist to specify a better link, bug 873093
   1.894 +      if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
   1.895 +        url = Services.urlFormatter.formatURLPref("plugins.update.url");
   1.896 +      }
   1.897 +      else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
   1.898 +        url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
   1.899 +      }
   1.900 +      else {
   1.901 +        url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
   1.902 +      }
   1.903 +      pluginInfo.detailsLink = url;
   1.904 +
   1.905 +      pluginData.set(pluginInfo.permissionString, pluginInfo);
   1.906 +    }
   1.907 +
   1.908 +    let primaryPluginPermission = null;
   1.909 +    if (aShowNow) {
   1.910 +      primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString;
   1.911 +    }
   1.912 +
   1.913 +    if (notification) {
   1.914 +      // Don't modify the notification UI while it's on the screen, that would be
   1.915 +      // jumpy and might allow clickjacking.
   1.916 +      if (aShowNow) {
   1.917 +        notification.options.primaryPlugin = primaryPluginPermission;
   1.918 +        notification.reshow();
   1.919 +        setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0);
   1.920 +      }
   1.921 +      return;
   1.922 +    }
   1.923 +
   1.924 +    let options = {
   1.925 +      dismissed: !aShowNow,
   1.926 +      eventCallback: this._clickToPlayNotificationEventCallback,
   1.927 +      primaryPlugin: primaryPluginPermission,
   1.928 +      pluginData: pluginData
   1.929 +    };
   1.930 +    PopupNotifications.show(aBrowser, "click-to-play-plugins",
   1.931 +                            "", "plugins-notification-icon",
   1.932 +                            null, null, options);
   1.933 +    setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0);
   1.934 +  },
   1.935 +
   1.936 +  _setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) {
   1.937 +    // Because this is called on a timeout, sanity-check before continuing
   1.938 +    if (!aBrowser.docShell || !aBrowser.contentWindow) {
   1.939 +      return;
   1.940 +    }
   1.941 +
   1.942 +    let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
   1.943 +    if (!notification)
   1.944 +      return;
   1.945 +
   1.946 +    // Make a copy of the actions, removing active plugins and checking for
   1.947 +    // outdated plugins.
   1.948 +    let haveInsecure = false;
   1.949 +    let actions = new Map();
   1.950 +    for (let action of notification.options.pluginData.values()) {
   1.951 +      switch (action.fallbackType) {
   1.952 +        // haveInsecure will trigger the red flashing icon and the infobar
   1.953 +        // styling below
   1.954 +        case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
   1.955 +        case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
   1.956 +          haveInsecure = true;
   1.957 +          // fall through
   1.958 +
   1.959 +        case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
   1.960 +          actions.set(action.permissionString, action);
   1.961 +          continue;
   1.962 +      }
   1.963 +    }
   1.964 +
   1.965 +    // check for hidden plugins
   1.966 +    let contentWindow = aBrowser.contentWindow;
   1.967 +    let contentDoc = aBrowser.contentDocument;
   1.968 +    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   1.969 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.970 +    for (let plugin of cwu.plugins) {
   1.971 +      let info = this._getPluginInfo(plugin);
   1.972 +      if (!actions.has(info.permissionString)) {
   1.973 +        continue;
   1.974 +      }
   1.975 +      let fallbackType = info.fallbackType;
   1.976 +      if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
   1.977 +        actions.delete(info.permissionString);
   1.978 +        if (actions.size == 0) {
   1.979 +          break;
   1.980 +        }
   1.981 +        continue;
   1.982 +      }
   1.983 +      if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
   1.984 +          fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
   1.985 +          fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
   1.986 +        continue;
   1.987 +      }
   1.988 +      let overlay = this.getPluginUI(plugin, "main");
   1.989 +      if (!overlay) {
   1.990 +        continue;
   1.991 +      }
   1.992 +      let shouldShow = this.shouldShowOverlay(plugin, overlay);
   1.993 +      this.setVisibility(plugin, overlay, shouldShow);
   1.994 +      if (shouldShow) {
   1.995 +        actions.delete(info.permissionString);
   1.996 +        if (actions.size == 0) {
   1.997 +          break;
   1.998 +        }
   1.999 +      }
  1.1000 +    }
  1.1001 +
  1.1002 +    // Set up the icon
  1.1003 +    document.getElementById("plugins-notification-icon").classList.
  1.1004 +      toggle("plugin-blocked", haveInsecure);
  1.1005 +
  1.1006 +    // Now configure the notification bar
  1.1007 +
  1.1008 +    let notificationBox = gBrowser.getNotificationBox(aBrowser);
  1.1009 +
  1.1010 +    function hideNotification() {
  1.1011 +      let n = notificationBox.getNotificationWithValue("plugin-hidden");
  1.1012 +      if (n) {
  1.1013 +        notificationBox.removeNotification(n, true);
  1.1014 +      }
  1.1015 +    }
  1.1016 +
  1.1017 +    // There are three different cases when showing an infobar:
  1.1018 +    // 1.  A single type of plugin is hidden on the page. Show the UI for that
  1.1019 +    //     plugin.
  1.1020 +    // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
  1.1021 +    //     with the vulnerable styling.
  1.1022 +    // 2b. Multiple types of plugins are hidden on the page, but none are
  1.1023 +    //     vulnerable. Show the nonvulnerable multi-UI.
  1.1024 +    function showNotification() {
  1.1025 +      let n = notificationBox.getNotificationWithValue("plugin-hidden");
  1.1026 +      if (n) {
  1.1027 +        // If something is already shown, just keep it
  1.1028 +        return;
  1.1029 +      }
  1.1030 +
  1.1031 +      Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN").
  1.1032 +        add(true);
  1.1033 +
  1.1034 +      let message;
  1.1035 +      // Icons set directly cannot be manipulated using moz-image-region, so
  1.1036 +      // we use CSS classes instead.
  1.1037 +      let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal);
  1.1038 +      let brand = document.getElementById("bundle_brand").getString("brandShortName");
  1.1039 +
  1.1040 +      if (actions.size == 1) {
  1.1041 +        let pluginInfo = [...actions.values()][0];
  1.1042 +        let pluginName = pluginInfo.pluginName;
  1.1043 +
  1.1044 +        switch (pluginInfo.fallbackType) {
  1.1045 +          case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
  1.1046 +            message = gNavigatorBundle.getFormattedString(
  1.1047 +              "pluginActivateNew.message",
  1.1048 +              [pluginName, host]);
  1.1049 +            break;
  1.1050 +          case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
  1.1051 +            message = gNavigatorBundle.getFormattedString(
  1.1052 +              "pluginActivateOutdated.message",
  1.1053 +              [pluginName, host, brand]);
  1.1054 +            break;
  1.1055 +          case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
  1.1056 +            message = gNavigatorBundle.getFormattedString(
  1.1057 +              "pluginActivateVulnerable.message",
  1.1058 +              [pluginName, host, brand]);
  1.1059 +        }
  1.1060 +      } else {
  1.1061 +        // Multi-plugin
  1.1062 +        message = gNavigatorBundle.getFormattedString(
  1.1063 +          "pluginActivateMultiple.message", [host]);
  1.1064 +
  1.1065 +        for (let action of actions.values()) {
  1.1066 +          if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) {
  1.1067 +            break;
  1.1068 +          }
  1.1069 +        }
  1.1070 +      }
  1.1071 +
  1.1072 +      let buttons = [
  1.1073 +        {
  1.1074 +          label: gNavigatorBundle.getString("pluginContinueBlocking.label"),
  1.1075 +          accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"),
  1.1076 +          callback: function() {
  1.1077 +            Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK").
  1.1078 +              add(true);
  1.1079 +
  1.1080 +            Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal,
  1.1081 +                                            "plugin-hidden-notification",
  1.1082 +                                            Services.perms.DENY_ACTION);
  1.1083 +          }
  1.1084 +        },
  1.1085 +        {
  1.1086 +          label: gNavigatorBundle.getString("pluginActivateTrigger.label"),
  1.1087 +          accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"),
  1.1088 +          callback: function() {
  1.1089 +            Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW").
  1.1090 +              add(true);
  1.1091 +
  1.1092 +            let curNotification =
  1.1093 +              PopupNotifications.getNotification("click-to-play-plugins",
  1.1094 +                                                 aBrowser);
  1.1095 +            if (curNotification) {
  1.1096 +              curNotification.reshow();
  1.1097 +            }
  1.1098 +          }
  1.1099 +        }
  1.1100 +      ];
  1.1101 +      n = notificationBox.
  1.1102 +        appendNotification(message, "plugin-hidden", null,
  1.1103 +                           notificationBox.PRIORITY_INFO_HIGH, buttons);
  1.1104 +      if (haveInsecure) {
  1.1105 +        n.classList.add('pluginVulnerable');
  1.1106 +      }
  1.1107 +    }
  1.1108 +
  1.1109 +    if (actions.size == 0) {
  1.1110 +      hideNotification();
  1.1111 +    } else {
  1.1112 +      let notificationPermission = Services.perms.testPermissionFromPrincipal(
  1.1113 +        aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification");
  1.1114 +      if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
  1.1115 +        hideNotification();
  1.1116 +      } else {
  1.1117 +        showNotification();
  1.1118 +      }
  1.1119 +    }
  1.1120 +  },
  1.1121 +
  1.1122 +  // Crashed-plugin observer. Notified once per plugin crash, before events
  1.1123 +  // are dispatched to individual plugin instances.
  1.1124 +  pluginCrashed : function(subject, topic, data) {
  1.1125 +    let propertyBag = subject;
  1.1126 +    if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
  1.1127 +        !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
  1.1128 +     return;
  1.1129 +
  1.1130 +#ifdef MOZ_CRASHREPORTER
  1.1131 +    let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
  1.1132 +    let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
  1.1133 +    let shouldSubmit = gCrashReporter.submitReports;
  1.1134 +    let doPrompt     = true; // XXX followup to get via gCrashReporter
  1.1135 +
  1.1136 +    // Submit automatically when appropriate.
  1.1137 +    if (pluginDumpID && shouldSubmit && !doPrompt) {
  1.1138 +      this.submitReport(pluginDumpID, browserDumpID);
  1.1139 +      // Submission is async, so we can't easily show failure UI.
  1.1140 +      propertyBag.setPropertyAsBool("submittedCrashReport", true);
  1.1141 +    }
  1.1142 +#endif
  1.1143 +  },
  1.1144 +
  1.1145 +  // Crashed-plugin event listener. Called for every instance of a
  1.1146 +  // plugin in content.
  1.1147 +  pluginInstanceCrashed: function (plugin, aEvent) {
  1.1148 +    // Ensure the plugin and event are of the right type.
  1.1149 +    if (!(aEvent instanceof Ci.nsIDOMCustomEvent))
  1.1150 +      return;
  1.1151 +
  1.1152 +    let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2);
  1.1153 +    let submittedReport = propBag.getPropertyAsBool("submittedCrashReport");
  1.1154 +    let doPrompt        = true; // XXX followup for .getPropertyAsBool("doPrompt");
  1.1155 +    let submitReports   = true; // XXX followup for .getPropertyAsBool("submitReports");
  1.1156 +    let pluginName      = propBag.getPropertyAsAString("pluginName");
  1.1157 +    let pluginDumpID    = propBag.getPropertyAsAString("pluginDumpID");
  1.1158 +    let browserDumpID   = propBag.getPropertyAsAString("browserDumpID");
  1.1159 +
  1.1160 +    // Remap the plugin name to a more user-presentable form.
  1.1161 +    pluginName = this.makeNicePluginName(pluginName);
  1.1162 +
  1.1163 +    let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
  1.1164 +
  1.1165 +    //
  1.1166 +    // Configure the crashed-plugin placeholder.
  1.1167 +    //
  1.1168 +
  1.1169 +    // Force a layout flush so the binding is attached.
  1.1170 +    plugin.clientTop;
  1.1171 +    let overlay = this.getPluginUI(plugin, "main");
  1.1172 +    let statusDiv = this.getPluginUI(plugin, "submitStatus");
  1.1173 +    let doc = plugin.ownerDocument;
  1.1174 +#ifdef MOZ_CRASHREPORTER
  1.1175 +    let status;
  1.1176 +
  1.1177 +    // Determine which message to show regarding crash reports.
  1.1178 +    if (submittedReport) { // submitReports && !doPrompt, handled in observer
  1.1179 +      status = "submitted";
  1.1180 +    }
  1.1181 +    else if (!submitReports && !doPrompt) {
  1.1182 +      status = "noSubmit";
  1.1183 +    }
  1.1184 +    else { // doPrompt
  1.1185 +      status = "please";
  1.1186 +      this.getPluginUI(plugin, "submitButton").addEventListener("click",
  1.1187 +        function (event) {
  1.1188 +          if (event.button != 0 || !event.isTrusted)
  1.1189 +            return;
  1.1190 +          this.submitReport(pluginDumpID, browserDumpID, plugin);
  1.1191 +          pref.setBoolPref("", optInCB.checked);
  1.1192 +        }.bind(this));
  1.1193 +      let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
  1.1194 +      let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
  1.1195 +      optInCB.checked = pref.getBoolPref("");
  1.1196 +    }
  1.1197 +
  1.1198 +    // If we don't have a minidumpID, we can't (or didn't) submit anything.
  1.1199 +    // This can happen if the plugin is killed from the task manager.
  1.1200 +    if (!pluginDumpID) {
  1.1201 +        status = "noReport";
  1.1202 +    }
  1.1203 +
  1.1204 +    statusDiv.setAttribute("status", status);
  1.1205 +
  1.1206 +    let helpIcon = this.getPluginUI(plugin, "helpIcon");
  1.1207 +    this.addLinkClickCallback(helpIcon, "openHelpPage");
  1.1208 +
  1.1209 +    // If we're showing the link to manually trigger report submission, we'll
  1.1210 +    // want to be able to update all the instances of the UI for this crash to
  1.1211 +    // show an updated message when a report is submitted.
  1.1212 +    if (doPrompt) {
  1.1213 +      let observer = {
  1.1214 +        QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
  1.1215 +                                               Ci.nsISupportsWeakReference]),
  1.1216 +        observe : function(subject, topic, data) {
  1.1217 +          let propertyBag = subject;
  1.1218 +          if (!(propertyBag instanceof Ci.nsIPropertyBag2))
  1.1219 +            return;
  1.1220 +          // Ignore notifications for other crashes.
  1.1221 +          if (propertyBag.get("minidumpID") != pluginDumpID)
  1.1222 +            return;
  1.1223 +          statusDiv.setAttribute("status", data);
  1.1224 +        },
  1.1225 +
  1.1226 +        handleEvent : function(event) {
  1.1227 +            // Not expected to be called, just here for the closure.
  1.1228 +        }
  1.1229 +      }
  1.1230 +
  1.1231 +      // Use a weak reference, so we don't have to remove it...
  1.1232 +      Services.obs.addObserver(observer, "crash-report-status", true);
  1.1233 +      // ...alas, now we need something to hold a strong reference to prevent
  1.1234 +      // it from being GC. But I don't want to manually manage the reference's
  1.1235 +      // lifetime (which should be no greater than the page).
  1.1236 +      // Clever solution? Use a closue with an event listener on the document.
  1.1237 +      // When the doc goes away, so do the listener references and the closure.
  1.1238 +      doc.addEventListener("mozCleverClosureHack", observer, false);
  1.1239 +    }
  1.1240 +#endif
  1.1241 +
  1.1242 +    let crashText = this.getPluginUI(plugin, "crashedText");
  1.1243 +    crashText.textContent = messageString;
  1.1244 +
  1.1245 +    let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
  1.1246 +
  1.1247 +    let link = this.getPluginUI(plugin, "reloadLink");
  1.1248 +    this.addLinkClickCallback(link, "reloadPage", browser);
  1.1249 +
  1.1250 +    let notificationBox = gBrowser.getNotificationBox(browser);
  1.1251 +
  1.1252 +    let isShowing = this.shouldShowOverlay(plugin, overlay);
  1.1253 +
  1.1254 +    // Is the <object>'s size too small to hold what we want to show?
  1.1255 +    if (!isShowing) {
  1.1256 +      // First try hiding the crash report submission UI.
  1.1257 +      statusDiv.removeAttribute("status");
  1.1258 +
  1.1259 +      isShowing = this.shouldShowOverlay(plugin, overlay);
  1.1260 +    }
  1.1261 +    this.setVisibility(plugin, overlay, isShowing);
  1.1262 +
  1.1263 +    if (isShowing) {
  1.1264 +      // If a previous plugin on the page was too small and resulted in adding a
  1.1265 +      // notification bar, then remove it because this plugin instance it big
  1.1266 +      // enough to serve as in-content notification.
  1.1267 +      hideNotificationBar();
  1.1268 +      doc.mozNoPluginCrashedNotification = true;
  1.1269 +    } else {
  1.1270 +      // If another plugin on the page was large enough to show our UI, we don't
  1.1271 +      // want to show a notification bar.
  1.1272 +      if (!doc.mozNoPluginCrashedNotification)
  1.1273 +        showNotificationBar(pluginDumpID, browserDumpID);
  1.1274 +    }
  1.1275 +
  1.1276 +    function hideNotificationBar() {
  1.1277 +      let notification = notificationBox.getNotificationWithValue("plugin-crashed");
  1.1278 +      if (notification)
  1.1279 +        notificationBox.removeNotification(notification, true);
  1.1280 +    }
  1.1281 +
  1.1282 +    function showNotificationBar(pluginDumpID, browserDumpID) {
  1.1283 +      // If there's already an existing notification bar, don't do anything.
  1.1284 +      let notification = notificationBox.getNotificationWithValue("plugin-crashed");
  1.1285 +      if (notification)
  1.1286 +        return;
  1.1287 +
  1.1288 +      // Configure the notification bar
  1.1289 +      let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
  1.1290 +      let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
  1.1291 +      let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
  1.1292 +      let reloadKey   = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
  1.1293 +      let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
  1.1294 +      let submitKey   = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
  1.1295 +
  1.1296 +      let buttons = [{
  1.1297 +        label: reloadLabel,
  1.1298 +        accessKey: reloadKey,
  1.1299 +        popup: null,
  1.1300 +        callback: function() { browser.reload(); },
  1.1301 +      }];
  1.1302 +#ifdef MOZ_CRASHREPORTER
  1.1303 +      let submitButton = {
  1.1304 +        label: submitLabel,
  1.1305 +        accessKey: submitKey,
  1.1306 +        popup: null,
  1.1307 +          callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
  1.1308 +      };
  1.1309 +      if (pluginDumpID)
  1.1310 +        buttons.push(submitButton);
  1.1311 +#endif
  1.1312 +
  1.1313 +      let notification = notificationBox.appendNotification(messageString, "plugin-crashed",
  1.1314 +                                                            iconURL, priority, buttons);
  1.1315 +
  1.1316 +      // Add the "learn more" link.
  1.1317 +      let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  1.1318 +      let link = notification.ownerDocument.createElementNS(XULNS, "label");
  1.1319 +      link.className = "text-link";
  1.1320 +      link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
  1.1321 +      let crashurl = formatURL("app.support.baseURL", true);
  1.1322 +      crashurl += "plugin-crashed-notificationbar";
  1.1323 +      link.href = crashurl;
  1.1324 +
  1.1325 +      let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
  1.1326 +      description.appendChild(link);
  1.1327 +
  1.1328 +      // Remove the notfication when the page is reloaded.
  1.1329 +      doc.defaultView.top.addEventListener("unload", function() {
  1.1330 +        notificationBox.removeNotification(notification);
  1.1331 +      }, false);
  1.1332 +    }
  1.1333 +
  1.1334 +  }
  1.1335 +};

mercurial