Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 5 | |
michael@0 | 6 | var gPluginHandler = { |
michael@0 | 7 | PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash", |
michael@0 | 8 | PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification", |
michael@0 | 9 | PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes", |
michael@0 | 10 | PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays", |
michael@0 | 11 | |
michael@0 | 12 | getPluginUI: function (plugin, anonid) { |
michael@0 | 13 | return plugin.ownerDocument. |
michael@0 | 14 | getAnonymousElementByAttribute(plugin, "anonid", anonid); |
michael@0 | 15 | }, |
michael@0 | 16 | |
michael@0 | 17 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 18 | get CrashSubmit() { |
michael@0 | 19 | delete this.CrashSubmit; |
michael@0 | 20 | Cu.import("resource://gre/modules/CrashSubmit.jsm", this); |
michael@0 | 21 | return this.CrashSubmit; |
michael@0 | 22 | }, |
michael@0 | 23 | #endif |
michael@0 | 24 | |
michael@0 | 25 | _getPluginInfo: function (pluginElement) { |
michael@0 | 26 | let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
michael@0 | 27 | pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); |
michael@0 | 28 | |
michael@0 | 29 | let tagMimetype; |
michael@0 | 30 | let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin"); |
michael@0 | 31 | let pluginTag = null; |
michael@0 | 32 | let permissionString = null; |
michael@0 | 33 | let fallbackType = null; |
michael@0 | 34 | let blocklistState = null; |
michael@0 | 35 | |
michael@0 | 36 | tagMimetype = pluginElement.actualType; |
michael@0 | 37 | if (tagMimetype == "") { |
michael@0 | 38 | tagMimetype = pluginElement.type; |
michael@0 | 39 | } |
michael@0 | 40 | |
michael@0 | 41 | if (gPluginHandler.isKnownPlugin(pluginElement)) { |
michael@0 | 42 | pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); |
michael@0 | 43 | pluginName = gPluginHandler.makeNicePluginName(pluginTag.name); |
michael@0 | 44 | |
michael@0 | 45 | permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType); |
michael@0 | 46 | fallbackType = pluginElement.defaultFallbackType; |
michael@0 | 47 | blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType); |
michael@0 | 48 | // Make state-softblocked == state-notblocked for our purposes, |
michael@0 | 49 | // they have the same UI. STATE_OUTDATED should not exist for plugin |
michael@0 | 50 | // items, but let's alias it anyway, just in case. |
michael@0 | 51 | if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED || |
michael@0 | 52 | blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { |
michael@0 | 53 | blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED; |
michael@0 | 54 | } |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | return { mimetype: tagMimetype, |
michael@0 | 58 | pluginName: pluginName, |
michael@0 | 59 | pluginTag: pluginTag, |
michael@0 | 60 | permissionString: permissionString, |
michael@0 | 61 | fallbackType: fallbackType, |
michael@0 | 62 | blocklistState: blocklistState, |
michael@0 | 63 | }; |
michael@0 | 64 | }, |
michael@0 | 65 | |
michael@0 | 66 | // Map the plugin's name to a filtered version more suitable for user UI. |
michael@0 | 67 | makeNicePluginName : function (aName) { |
michael@0 | 68 | if (aName == "Shockwave Flash") |
michael@0 | 69 | return "Adobe Flash"; |
michael@0 | 70 | |
michael@0 | 71 | // Clean up the plugin name by stripping off parenthetical clauses, |
michael@0 | 72 | // trailing version numbers or "plugin". |
michael@0 | 73 | // EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar" |
michael@0 | 74 | // Do this by first stripping the numbers, etc. off the end, and then |
michael@0 | 75 | // removing "Plugin" (and then trimming to get rid of any whitespace). |
michael@0 | 76 | // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled) |
michael@0 | 77 | let newName = aName.replace(/\(.*?\)/g, ""). |
michael@0 | 78 | replace(/[\s\d\.\-\_\(\)]+$/, ""). |
michael@0 | 79 | replace(/\bplug-?in\b/i, "").trim(); |
michael@0 | 80 | return newName; |
michael@0 | 81 | }, |
michael@0 | 82 | |
michael@0 | 83 | /** |
michael@0 | 84 | * Update the visibility of the plugin overlay. |
michael@0 | 85 | */ |
michael@0 | 86 | setVisibility : function (plugin, overlay, shouldShow) { |
michael@0 | 87 | overlay.classList.toggle("visible", shouldShow); |
michael@0 | 88 | }, |
michael@0 | 89 | |
michael@0 | 90 | /** |
michael@0 | 91 | * Check whether the plugin should be visible on the page. A plugin should |
michael@0 | 92 | * not be visible if the overlay is too big, or if any other page content |
michael@0 | 93 | * overlays it. |
michael@0 | 94 | * |
michael@0 | 95 | * This function will handle showing or hiding the overlay. |
michael@0 | 96 | * @returns true if the plugin is invisible. |
michael@0 | 97 | */ |
michael@0 | 98 | shouldShowOverlay : function (plugin, overlay) { |
michael@0 | 99 | // If the overlay size is 0, we haven't done layout yet. Presume that |
michael@0 | 100 | // plugins are visible until we know otherwise. |
michael@0 | 101 | if (overlay.scrollWidth == 0) { |
michael@0 | 102 | return true; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | // Is the <object>'s size too small to hold what we want to show? |
michael@0 | 106 | let pluginRect = plugin.getBoundingClientRect(); |
michael@0 | 107 | // XXX bug 446693. The text-shadow on the submitted-report text at |
michael@0 | 108 | // the bottom causes scrollHeight to be larger than it should be. |
michael@0 | 109 | let overflows = (overlay.scrollWidth > pluginRect.width) || |
michael@0 | 110 | (overlay.scrollHeight - 5 > pluginRect.height); |
michael@0 | 111 | if (overflows) { |
michael@0 | 112 | return false; |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | // Is the plugin covered up by other content so that it is not clickable? |
michael@0 | 116 | // Floating point can confuse .elementFromPoint, so inset just a bit |
michael@0 | 117 | let left = pluginRect.left + 2; |
michael@0 | 118 | let right = pluginRect.right - 2; |
michael@0 | 119 | let top = pluginRect.top + 2; |
michael@0 | 120 | let bottom = pluginRect.bottom - 2; |
michael@0 | 121 | let centerX = left + (right - left) / 2; |
michael@0 | 122 | let centerY = top + (bottom - top) / 2; |
michael@0 | 123 | let points = [[left, top], |
michael@0 | 124 | [left, bottom], |
michael@0 | 125 | [right, top], |
michael@0 | 126 | [right, bottom], |
michael@0 | 127 | [centerX, centerY]]; |
michael@0 | 128 | |
michael@0 | 129 | if (right <= 0 || top <= 0) { |
michael@0 | 130 | return false; |
michael@0 | 131 | } |
michael@0 | 132 | |
michael@0 | 133 | let contentWindow = plugin.ownerDocument.defaultView; |
michael@0 | 134 | let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 135 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 136 | |
michael@0 | 137 | for (let [x, y] of points) { |
michael@0 | 138 | let el = cwu.elementFromPoint(x, y, true, true); |
michael@0 | 139 | if (el !== plugin) { |
michael@0 | 140 | return false; |
michael@0 | 141 | } |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | return true; |
michael@0 | 145 | }, |
michael@0 | 146 | |
michael@0 | 147 | addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) { |
michael@0 | 148 | // XXX just doing (callback)(arg) was giving a same-origin error. bug? |
michael@0 | 149 | let self = this; |
michael@0 | 150 | let callbackArgs = Array.prototype.slice.call(arguments).slice(2); |
michael@0 | 151 | linkNode.addEventListener("click", |
michael@0 | 152 | function(evt) { |
michael@0 | 153 | if (!evt.isTrusted) |
michael@0 | 154 | return; |
michael@0 | 155 | evt.preventDefault(); |
michael@0 | 156 | if (callbackArgs.length == 0) |
michael@0 | 157 | callbackArgs = [ evt ]; |
michael@0 | 158 | (self[callbackName]).apply(self, callbackArgs); |
michael@0 | 159 | }, |
michael@0 | 160 | true); |
michael@0 | 161 | |
michael@0 | 162 | linkNode.addEventListener("keydown", |
michael@0 | 163 | function(evt) { |
michael@0 | 164 | if (!evt.isTrusted) |
michael@0 | 165 | return; |
michael@0 | 166 | if (evt.keyCode == evt.DOM_VK_RETURN) { |
michael@0 | 167 | evt.preventDefault(); |
michael@0 | 168 | if (callbackArgs.length == 0) |
michael@0 | 169 | callbackArgs = [ evt ]; |
michael@0 | 170 | evt.preventDefault(); |
michael@0 | 171 | (self[callbackName]).apply(self, callbackArgs); |
michael@0 | 172 | } |
michael@0 | 173 | }, |
michael@0 | 174 | true); |
michael@0 | 175 | }, |
michael@0 | 176 | |
michael@0 | 177 | // Helper to get the binding handler type from a plugin object |
michael@0 | 178 | _getBindingType : function(plugin) { |
michael@0 | 179 | if (!(plugin instanceof Ci.nsIObjectLoadingContent)) |
michael@0 | 180 | return null; |
michael@0 | 181 | |
michael@0 | 182 | switch (plugin.pluginFallbackType) { |
michael@0 | 183 | case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: |
michael@0 | 184 | return "PluginNotFound"; |
michael@0 | 185 | case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED: |
michael@0 | 186 | return "PluginDisabled"; |
michael@0 | 187 | case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED: |
michael@0 | 188 | return "PluginBlocklisted"; |
michael@0 | 189 | case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED: |
michael@0 | 190 | return "PluginOutdated"; |
michael@0 | 191 | case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: |
michael@0 | 192 | return "PluginClickToPlay"; |
michael@0 | 193 | case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: |
michael@0 | 194 | return "PluginVulnerableUpdatable"; |
michael@0 | 195 | case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: |
michael@0 | 196 | return "PluginVulnerableNoUpdate"; |
michael@0 | 197 | case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW: |
michael@0 | 198 | return "PluginPlayPreview"; |
michael@0 | 199 | default: |
michael@0 | 200 | // Not all states map to a handler |
michael@0 | 201 | return null; |
michael@0 | 202 | } |
michael@0 | 203 | }, |
michael@0 | 204 | |
michael@0 | 205 | supportedPlugins: { |
michael@0 | 206 | "mimetypes": { |
michael@0 | 207 | "application/x-shockwave-flash": "flash", |
michael@0 | 208 | "application/futuresplash": "flash", |
michael@0 | 209 | "application/x-java-.*": "java", |
michael@0 | 210 | "application/x-director": "shockwave", |
michael@0 | 211 | "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime", |
michael@0 | 212 | "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime", |
michael@0 | 213 | "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime", |
michael@0 | 214 | "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime", |
michael@0 | 215 | "application/x-unknown": "test", |
michael@0 | 216 | }, |
michael@0 | 217 | |
michael@0 | 218 | "plugins": { |
michael@0 | 219 | "flash": { |
michael@0 | 220 | "displayName": "Flash", |
michael@0 | 221 | "installWINNT": true, |
michael@0 | 222 | "installDarwin": true, |
michael@0 | 223 | "installLinux": true, |
michael@0 | 224 | }, |
michael@0 | 225 | "java": { |
michael@0 | 226 | "displayName": "Java", |
michael@0 | 227 | "installWINNT": true, |
michael@0 | 228 | "installDarwin": true, |
michael@0 | 229 | "installLinux": true, |
michael@0 | 230 | }, |
michael@0 | 231 | "shockwave": { |
michael@0 | 232 | "displayName": "Shockwave", |
michael@0 | 233 | "installWINNT": true, |
michael@0 | 234 | "installDarwin": true, |
michael@0 | 235 | }, |
michael@0 | 236 | "quicktime": { |
michael@0 | 237 | "displayName": "QuickTime", |
michael@0 | 238 | "installWINNT": true, |
michael@0 | 239 | }, |
michael@0 | 240 | "test": { |
michael@0 | 241 | "displayName": "Test plugin", |
michael@0 | 242 | "installWINNT": true, |
michael@0 | 243 | "installLinux": true, |
michael@0 | 244 | "installDarwin": true, |
michael@0 | 245 | } |
michael@0 | 246 | } |
michael@0 | 247 | }, |
michael@0 | 248 | |
michael@0 | 249 | nameForSupportedPlugin: function (aMimeType) { |
michael@0 | 250 | for (let type in this.supportedPlugins.mimetypes) { |
michael@0 | 251 | let re = new RegExp(type); |
michael@0 | 252 | if (re.test(aMimeType)) { |
michael@0 | 253 | return this.supportedPlugins.mimetypes[type]; |
michael@0 | 254 | } |
michael@0 | 255 | } |
michael@0 | 256 | return null; |
michael@0 | 257 | }, |
michael@0 | 258 | |
michael@0 | 259 | canInstallThisMimeType: function (aMimeType) { |
michael@0 | 260 | let os = Services.appinfo.OS; |
michael@0 | 261 | let pluginName = this.nameForSupportedPlugin(aMimeType); |
michael@0 | 262 | if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) { |
michael@0 | 263 | return true; |
michael@0 | 264 | } |
michael@0 | 265 | return false; |
michael@0 | 266 | }, |
michael@0 | 267 | |
michael@0 | 268 | handleEvent : function(event) { |
michael@0 | 269 | let eventType = event.type; |
michael@0 | 270 | |
michael@0 | 271 | if (eventType == "PluginRemoved") { |
michael@0 | 272 | let doc = event.target; |
michael@0 | 273 | let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
michael@0 | 274 | if (browser) |
michael@0 | 275 | this._setPluginNotificationIcon(browser); |
michael@0 | 276 | return; |
michael@0 | 277 | } |
michael@0 | 278 | |
michael@0 | 279 | let plugin = event.target; |
michael@0 | 280 | let doc = plugin.ownerDocument; |
michael@0 | 281 | |
michael@0 | 282 | if (!(plugin instanceof Ci.nsIObjectLoadingContent)) |
michael@0 | 283 | return; |
michael@0 | 284 | |
michael@0 | 285 | if (eventType == "PluginBindingAttached") { |
michael@0 | 286 | // The plugin binding fires this event when it is created. |
michael@0 | 287 | // As an untrusted event, ensure that this object actually has a binding |
michael@0 | 288 | // and make sure we don't handle it twice |
michael@0 | 289 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 290 | if (!overlay || overlay._bindingHandled) { |
michael@0 | 291 | return; |
michael@0 | 292 | } |
michael@0 | 293 | overlay._bindingHandled = true; |
michael@0 | 294 | |
michael@0 | 295 | // Lookup the handler for this binding |
michael@0 | 296 | eventType = this._getBindingType(plugin); |
michael@0 | 297 | if (!eventType) { |
michael@0 | 298 | // Not all bindings have handlers |
michael@0 | 299 | return; |
michael@0 | 300 | } |
michael@0 | 301 | } |
michael@0 | 302 | |
michael@0 | 303 | let shouldShowNotification = false; |
michael@0 | 304 | let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
michael@0 | 305 | if (!browser) |
michael@0 | 306 | return; |
michael@0 | 307 | |
michael@0 | 308 | switch (eventType) { |
michael@0 | 309 | case "PluginCrashed": |
michael@0 | 310 | this.pluginInstanceCrashed(plugin, event); |
michael@0 | 311 | break; |
michael@0 | 312 | |
michael@0 | 313 | case "PluginNotFound": |
michael@0 | 314 | let installable = this.showInstallNotification(plugin, eventType); |
michael@0 | 315 | // For non-object plugin tags, register a click handler to install the |
michael@0 | 316 | // plugin. Object tags can, and often do, deal with that themselves, |
michael@0 | 317 | // so don't stomp on the page developers toes. |
michael@0 | 318 | if (installable && !(plugin instanceof HTMLObjectElement)) { |
michael@0 | 319 | let installStatus = this.getPluginUI(plugin, "installStatus"); |
michael@0 | 320 | installStatus.setAttribute("installable", "true"); |
michael@0 | 321 | let iconStatus = this.getPluginUI(plugin, "icon"); |
michael@0 | 322 | iconStatus.setAttribute("installable", "true"); |
michael@0 | 323 | |
michael@0 | 324 | let installLink = this.getPluginUI(plugin, "installPluginLink"); |
michael@0 | 325 | this.addLinkClickCallback(installLink, "installSinglePlugin", plugin); |
michael@0 | 326 | } |
michael@0 | 327 | break; |
michael@0 | 328 | |
michael@0 | 329 | case "PluginBlocklisted": |
michael@0 | 330 | case "PluginOutdated": |
michael@0 | 331 | shouldShowNotification = true; |
michael@0 | 332 | break; |
michael@0 | 333 | |
michael@0 | 334 | case "PluginVulnerableUpdatable": |
michael@0 | 335 | let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink"); |
michael@0 | 336 | this.addLinkClickCallback(updateLink, "openPluginUpdatePage"); |
michael@0 | 337 | /* FALLTHRU */ |
michael@0 | 338 | |
michael@0 | 339 | case "PluginVulnerableNoUpdate": |
michael@0 | 340 | case "PluginClickToPlay": |
michael@0 | 341 | this._handleClickToPlayEvent(plugin); |
michael@0 | 342 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 343 | let pluginName = this._getPluginInfo(plugin).pluginName; |
michael@0 | 344 | let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]); |
michael@0 | 345 | let overlayText = this.getPluginUI(plugin, "clickToPlay"); |
michael@0 | 346 | overlayText.textContent = messageString; |
michael@0 | 347 | if (eventType == "PluginVulnerableUpdatable" || |
michael@0 | 348 | eventType == "PluginVulnerableNoUpdate") { |
michael@0 | 349 | let vulnerabilityString = gNavigatorBundle.getString(eventType); |
michael@0 | 350 | let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus"); |
michael@0 | 351 | vulnerabilityText.textContent = vulnerabilityString; |
michael@0 | 352 | } |
michael@0 | 353 | shouldShowNotification = true; |
michael@0 | 354 | break; |
michael@0 | 355 | |
michael@0 | 356 | case "PluginPlayPreview": |
michael@0 | 357 | this._handlePlayPreviewEvent(plugin); |
michael@0 | 358 | break; |
michael@0 | 359 | |
michael@0 | 360 | case "PluginDisabled": |
michael@0 | 361 | // Screw the disabled message. It messes with HTML5 fallback on YouTube |
michael@0 | 362 | let plugin_overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); |
michael@0 | 363 | if (plugin_overlay != null) |
michael@0 | 364 | plugin_overlay.style.visibility = "hidden"; |
michael@0 | 365 | break; |
michael@0 | 366 | |
michael@0 | 367 | case "PluginInstantiated": |
michael@0 | 368 | shouldShowNotification = true; |
michael@0 | 369 | break; |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | // Show the in-content UI if it's not too big. The crashed plugin handler already did this. |
michael@0 | 373 | if (eventType != "PluginCrashed") { |
michael@0 | 374 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 375 | if (overlay != null) { |
michael@0 | 376 | this.setVisibility(plugin, overlay, |
michael@0 | 377 | this.shouldShowOverlay(plugin, overlay)); |
michael@0 | 378 | let resizeListener = (event) => { |
michael@0 | 379 | this.setVisibility(plugin, overlay, |
michael@0 | 380 | this.shouldShowOverlay(plugin, overlay)); |
michael@0 | 381 | this._setPluginNotificationIcon(browser); |
michael@0 | 382 | }; |
michael@0 | 383 | plugin.addEventListener("overflow", resizeListener); |
michael@0 | 384 | plugin.addEventListener("underflow", resizeListener); |
michael@0 | 385 | } |
michael@0 | 386 | } |
michael@0 | 387 | |
michael@0 | 388 | let closeIcon = this.getPluginUI(plugin, "closeIcon"); |
michael@0 | 389 | if (closeIcon) { |
michael@0 | 390 | closeIcon.addEventListener("click", function(aEvent) { |
michael@0 | 391 | if (aEvent.button == 0 && aEvent.isTrusted) |
michael@0 | 392 | gPluginHandler.hideClickToPlayOverlay(plugin); |
michael@0 | 393 | }, true); |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | if (shouldShowNotification) { |
michael@0 | 397 | this._showClickToPlayNotification(browser, plugin, false); |
michael@0 | 398 | } |
michael@0 | 399 | }, |
michael@0 | 400 | |
michael@0 | 401 | isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) { |
michael@0 | 402 | return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == |
michael@0 | 403 | Ci.nsIObjectLoadingContent.TYPE_PLUGIN); |
michael@0 | 404 | }, |
michael@0 | 405 | |
michael@0 | 406 | canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) { |
michael@0 | 407 | // if this isn't a known plugin, we can't activate it |
michael@0 | 408 | // (this also guards pluginHost.getPermissionStringForType against |
michael@0 | 409 | // unexpected input) |
michael@0 | 410 | if (!gPluginHandler.isKnownPlugin(objLoadingContent)) |
michael@0 | 411 | return false; |
michael@0 | 412 | |
michael@0 | 413 | let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
michael@0 | 414 | let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); |
michael@0 | 415 | let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal; |
michael@0 | 416 | let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); |
michael@0 | 417 | |
michael@0 | 418 | let isFallbackTypeValid = |
michael@0 | 419 | objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && |
michael@0 | 420 | objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; |
michael@0 | 421 | |
michael@0 | 422 | if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) { |
michael@0 | 423 | // checking if play preview is subject to CTP rules |
michael@0 | 424 | let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType); |
michael@0 | 425 | isFallbackTypeValid = !playPreviewInfo.ignoreCTP; |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | return !objLoadingContent.activated && |
michael@0 | 429 | pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && |
michael@0 | 430 | isFallbackTypeValid; |
michael@0 | 431 | }, |
michael@0 | 432 | |
michael@0 | 433 | hideClickToPlayOverlay: function(aPlugin) { |
michael@0 | 434 | let overlay = this.getPluginUI(aPlugin, "main"); |
michael@0 | 435 | if (overlay) { |
michael@0 | 436 | overlay.classList.remove("visible"); |
michael@0 | 437 | } |
michael@0 | 438 | }, |
michael@0 | 439 | |
michael@0 | 440 | stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) { |
michael@0 | 441 | let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); |
michael@0 | 442 | if (objLoadingContent.activated) |
michael@0 | 443 | return; |
michael@0 | 444 | |
michael@0 | 445 | if (aPlayPlugin) |
michael@0 | 446 | objLoadingContent.playPlugin(); |
michael@0 | 447 | else |
michael@0 | 448 | objLoadingContent.cancelPlayPreview(); |
michael@0 | 449 | }, |
michael@0 | 450 | |
michael@0 | 451 | newPluginInstalled : function(event) { |
michael@0 | 452 | // browser elements are anonymous so we can't just use target. |
michael@0 | 453 | var browser = event.originalTarget; |
michael@0 | 454 | // clear the plugin list, now that at least one plugin has been installed |
michael@0 | 455 | browser.missingPlugins = null; |
michael@0 | 456 | |
michael@0 | 457 | var notificationBox = gBrowser.getNotificationBox(browser); |
michael@0 | 458 | var notification = notificationBox.getNotificationWithValue("missing-plugins"); |
michael@0 | 459 | if (notification) |
michael@0 | 460 | notificationBox.removeNotification(notification); |
michael@0 | 461 | |
michael@0 | 462 | // reload the browser to make the new plugin show. |
michael@0 | 463 | browser.reload(); |
michael@0 | 464 | }, |
michael@0 | 465 | |
michael@0 | 466 | // Callback for user clicking on a missing (unsupported) plugin. |
michael@0 | 467 | installSinglePlugin: function (plugin) { |
michael@0 | 468 | var missingPlugins = new Map(); |
michael@0 | 469 | |
michael@0 | 470 | var pluginInfo = this._getPluginInfo(plugin); |
michael@0 | 471 | missingPlugins.set(pluginInfo.mimetype, pluginInfo); |
michael@0 | 472 | |
michael@0 | 473 | openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", |
michael@0 | 474 | "PFSWindow", "chrome,centerscreen,resizable=yes", |
michael@0 | 475 | {plugins: missingPlugins, browser: gBrowser.selectedBrowser}); |
michael@0 | 476 | }, |
michael@0 | 477 | |
michael@0 | 478 | // Callback for user clicking on a disabled plugin |
michael@0 | 479 | managePlugins: function (aEvent) { |
michael@0 | 480 | BrowserOpenAddonsMgr("addons://list/plugin"); |
michael@0 | 481 | }, |
michael@0 | 482 | |
michael@0 | 483 | // Callback for user clicking on the link in a click-to-play plugin |
michael@0 | 484 | // (where the plugin has an update) |
michael@0 | 485 | openPluginUpdatePage: function (aEvent) { |
michael@0 | 486 | openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab"); |
michael@0 | 487 | }, |
michael@0 | 488 | |
michael@0 | 489 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 490 | submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) { |
michael@0 | 491 | let keyVals = {}; |
michael@0 | 492 | if (plugin) { |
michael@0 | 493 | let userComment = this.getPluginUI(plugin, "submitComment").value.trim(); |
michael@0 | 494 | if (userComment) |
michael@0 | 495 | keyVals.PluginUserComment = userComment; |
michael@0 | 496 | if (this.getPluginUI(plugin, "submitURLOptIn").checked) |
michael@0 | 497 | keyVals.PluginContentURL = plugin.ownerDocument.URL; |
michael@0 | 498 | } |
michael@0 | 499 | this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals }); |
michael@0 | 500 | if (browserDumpID) |
michael@0 | 501 | this.CrashSubmit.submit(browserDumpID); |
michael@0 | 502 | }, |
michael@0 | 503 | #endif |
michael@0 | 504 | |
michael@0 | 505 | // Callback for user clicking a "reload page" link |
michael@0 | 506 | reloadPage: function (browser) { |
michael@0 | 507 | browser.reload(); |
michael@0 | 508 | }, |
michael@0 | 509 | |
michael@0 | 510 | // Callback for user clicking the help icon |
michael@0 | 511 | openHelpPage: function () { |
michael@0 | 512 | openHelpLink("plugin-crashed", false); |
michael@0 | 513 | }, |
michael@0 | 514 | |
michael@0 | 515 | showInstallNotification: function (aPlugin) { |
michael@0 | 516 | let hideMissingPluginsNotification = |
michael@0 | 517 | Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION); |
michael@0 | 518 | if (hideMissingPluginsNotification) { |
michael@0 | 519 | return false; |
michael@0 | 520 | } |
michael@0 | 521 | |
michael@0 | 522 | let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument |
michael@0 | 523 | .defaultView.top.document); |
michael@0 | 524 | if (!browser.missingPlugins) |
michael@0 | 525 | browser.missingPlugins = new Map(); |
michael@0 | 526 | |
michael@0 | 527 | let pluginInfo = this._getPluginInfo(aPlugin); |
michael@0 | 528 | browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo); |
michael@0 | 529 | |
michael@0 | 530 | // only show notification for small subset of plugins |
michael@0 | 531 | let mimetype = pluginInfo.mimetype.split(";")[0]; |
michael@0 | 532 | if (!this.canInstallThisMimeType(mimetype)) |
michael@0 | 533 | return false; |
michael@0 | 534 | |
michael@0 | 535 | let pluginIdentifier = this.nameForSupportedPlugin(mimetype); |
michael@0 | 536 | if (!pluginIdentifier) |
michael@0 | 537 | return false; |
michael@0 | 538 | |
michael@0 | 539 | let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName; |
michael@0 | 540 | |
michael@0 | 541 | // don't show several notifications |
michael@0 | 542 | let notification = PopupNotifications.getNotification("plugins-not-found", browser); |
michael@0 | 543 | if (notification) |
michael@0 | 544 | return true; |
michael@0 | 545 | |
michael@0 | 546 | let messageString = gNavigatorBundle.getString("installPlugin.message"); |
michael@0 | 547 | let mainAction = { |
michael@0 | 548 | label: gNavigatorBundle.getFormattedString("installPlugin.button.label", |
michael@0 | 549 | [displayName]), |
michael@0 | 550 | accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"), |
michael@0 | 551 | callback: function () { |
michael@0 | 552 | openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", |
michael@0 | 553 | "PFSWindow", "chrome,centerscreen,resizable=yes", |
michael@0 | 554 | {plugins: browser.missingPlugins, browser: browser}); |
michael@0 | 555 | } |
michael@0 | 556 | }; |
michael@0 | 557 | let secondaryActions = null; |
michael@0 | 558 | let options = { dismissed: true }; |
michael@0 | 559 | |
michael@0 | 560 | let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH); |
michael@0 | 561 | if (pluginIdentifier == "flash" && showForFlash) { |
michael@0 | 562 | let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH; |
michael@0 | 563 | secondaryActions = [{ |
michael@0 | 564 | label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"), |
michael@0 | 565 | accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"), |
michael@0 | 566 | callback: function () { |
michael@0 | 567 | Services.prefs.setBoolPref(prefNotifyMissingFlash, false); |
michael@0 | 568 | } |
michael@0 | 569 | }]; |
michael@0 | 570 | options.dismissed = false; |
michael@0 | 571 | } |
michael@0 | 572 | PopupNotifications.show(browser, "plugins-not-found", |
michael@0 | 573 | messageString, "plugin-install-notification-icon", |
michael@0 | 574 | mainAction, secondaryActions, options); |
michael@0 | 575 | return true; |
michael@0 | 576 | }, |
michael@0 | 577 | // Event listener for click-to-play plugins. |
michael@0 | 578 | _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) { |
michael@0 | 579 | let doc = aPlugin.ownerDocument; |
michael@0 | 580 | let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
michael@0 | 581 | let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
michael@0 | 582 | let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); |
michael@0 | 583 | // guard against giving pluginHost.getPermissionStringForType a type |
michael@0 | 584 | // not associated with any known plugin |
michael@0 | 585 | if (!gPluginHandler.isKnownPlugin(objLoadingContent)) |
michael@0 | 586 | return; |
michael@0 | 587 | let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); |
michael@0 | 588 | let principal = doc.defaultView.top.document.nodePrincipal; |
michael@0 | 589 | let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); |
michael@0 | 590 | |
michael@0 | 591 | let overlay = this.getPluginUI(aPlugin, "main"); |
michael@0 | 592 | |
michael@0 | 593 | if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { |
michael@0 | 594 | if (overlay) { |
michael@0 | 595 | overlay.classList.remove("visible"); |
michael@0 | 596 | } |
michael@0 | 597 | return; |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | if (overlay) { |
michael@0 | 601 | overlay.addEventListener("click", gPluginHandler._overlayClickListener, true); |
michael@0 | 602 | } |
michael@0 | 603 | }, |
michael@0 | 604 | |
michael@0 | 605 | _overlayClickListener: { |
michael@0 | 606 | handleEvent: function PH_handleOverlayClick(aEvent) { |
michael@0 | 607 | let plugin = document.getBindingParent(aEvent.target); |
michael@0 | 608 | let contentWindow = plugin.ownerDocument.defaultView.top; |
michael@0 | 609 | // gBrowser.getBrowserForDocument does not exist in the case where we |
michael@0 | 610 | // drag-and-dropped a tab from a window containing only that tab. In |
michael@0 | 611 | // that case, the window gets destroyed. |
michael@0 | 612 | let browser = gBrowser.getBrowserForDocument ? |
michael@0 | 613 | gBrowser.getBrowserForDocument(contentWindow.document) : |
michael@0 | 614 | null; |
michael@0 | 615 | // If browser is null here, we've been drag-and-dropped from another |
michael@0 | 616 | // window, and this is the wrong click handler. |
michael@0 | 617 | if (!browser) { |
michael@0 | 618 | aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true); |
michael@0 | 619 | return; |
michael@0 | 620 | } |
michael@0 | 621 | let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); |
michael@0 | 622 | // Have to check that the target is not the link to update the plugin |
michael@0 | 623 | if (!(aEvent.originalTarget instanceof HTMLAnchorElement) && |
michael@0 | 624 | (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') && |
michael@0 | 625 | aEvent.button == 0 && aEvent.isTrusted) { |
michael@0 | 626 | gPluginHandler._showClickToPlayNotification(browser, plugin, true); |
michael@0 | 627 | aEvent.stopPropagation(); |
michael@0 | 628 | aEvent.preventDefault(); |
michael@0 | 629 | } |
michael@0 | 630 | } |
michael@0 | 631 | }, |
michael@0 | 632 | |
michael@0 | 633 | _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) { |
michael@0 | 634 | let doc = aPlugin.ownerDocument; |
michael@0 | 635 | let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
michael@0 | 636 | let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
michael@0 | 637 | let pluginInfo = this._getPluginInfo(aPlugin); |
michael@0 | 638 | let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype); |
michael@0 | 639 | |
michael@0 | 640 | let previewContent = this.getPluginUI(aPlugin, "previewPluginContent"); |
michael@0 | 641 | let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; |
michael@0 | 642 | if (!iframe) { |
michael@0 | 643 | // lazy initialization of the iframe |
michael@0 | 644 | iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); |
michael@0 | 645 | iframe.className = "previewPluginContentFrame"; |
michael@0 | 646 | previewContent.appendChild(iframe); |
michael@0 | 647 | |
michael@0 | 648 | // Force a style flush, so that we ensure our binding is attached. |
michael@0 | 649 | aPlugin.clientTop; |
michael@0 | 650 | } |
michael@0 | 651 | iframe.src = playPreviewInfo.redirectURL; |
michael@0 | 652 | |
michael@0 | 653 | // MozPlayPlugin event can be dispatched from the extension chrome |
michael@0 | 654 | // code to replace the preview content with the native plugin |
michael@0 | 655 | previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) { |
michael@0 | 656 | if (!aEvent.isTrusted) |
michael@0 | 657 | return; |
michael@0 | 658 | |
michael@0 | 659 | previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); |
michael@0 | 660 | |
michael@0 | 661 | let playPlugin = !aEvent.detail; |
michael@0 | 662 | gPluginHandler.stopPlayPreview(aPlugin, playPlugin); |
michael@0 | 663 | |
michael@0 | 664 | // cleaning up: removes overlay iframe from the DOM |
michael@0 | 665 | let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; |
michael@0 | 666 | if (iframe) |
michael@0 | 667 | previewContent.removeChild(iframe); |
michael@0 | 668 | }, true); |
michael@0 | 669 | |
michael@0 | 670 | if (!playPreviewInfo.ignoreCTP) { |
michael@0 | 671 | gPluginHandler._showClickToPlayNotification(browser, aPlugin, false); |
michael@0 | 672 | } |
michael@0 | 673 | }, |
michael@0 | 674 | |
michael@0 | 675 | reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() { |
michael@0 | 676 | let browser = gBrowser.selectedBrowser; |
michael@0 | 677 | let contentWindow = browser.contentWindow; |
michael@0 | 678 | let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 679 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 680 | let plugins = cwu.plugins; |
michael@0 | 681 | for (let plugin of plugins) { |
michael@0 | 682 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 683 | if (overlay) |
michael@0 | 684 | overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); |
michael@0 | 685 | let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); |
michael@0 | 686 | if (gPluginHandler.canActivatePlugin(objLoadingContent)) |
michael@0 | 687 | gPluginHandler._handleClickToPlayEvent(plugin); |
michael@0 | 688 | } |
michael@0 | 689 | gPluginHandler._showClickToPlayNotification(browser, null, false); |
michael@0 | 690 | }, |
michael@0 | 691 | |
michael@0 | 692 | _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) { |
michael@0 | 693 | if (event == "showing") { |
michael@0 | 694 | Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN") |
michael@0 | 695 | .add(!this.options.primaryPlugin); |
michael@0 | 696 | // Histograms always start at 0, even though our data starts at 1 |
michael@0 | 697 | let histogramCount = this.options.pluginData.size - 1; |
michael@0 | 698 | if (histogramCount > 4) { |
michael@0 | 699 | histogramCount = 4; |
michael@0 | 700 | } |
michael@0 | 701 | Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT") |
michael@0 | 702 | .add(histogramCount); |
michael@0 | 703 | } |
michael@0 | 704 | else if (event == "dismissed") { |
michael@0 | 705 | // Once the popup is dismissed, clicking the icon should show the full |
michael@0 | 706 | // list again |
michael@0 | 707 | this.options.primaryPlugin = null; |
michael@0 | 708 | } |
michael@0 | 709 | }, |
michael@0 | 710 | |
michael@0 | 711 | // Match the behaviour of nsPermissionManager |
michael@0 | 712 | _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) { |
michael@0 | 713 | if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) { |
michael@0 | 714 | return "(null)"; |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | try { |
michael@0 | 718 | if (principal.URI.host) |
michael@0 | 719 | return principal.URI.host; |
michael@0 | 720 | } catch (e) {} |
michael@0 | 721 | |
michael@0 | 722 | return principal.origin; |
michael@0 | 723 | }, |
michael@0 | 724 | |
michael@0 | 725 | /** |
michael@0 | 726 | * Called from the plugin doorhanger to set the new permissions for a plugin |
michael@0 | 727 | * and activate plugins if necessary. |
michael@0 | 728 | * aNewState should be either "allownow" "allowalways" or "block" |
michael@0 | 729 | */ |
michael@0 | 730 | _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) { |
michael@0 | 731 | let permission; |
michael@0 | 732 | let expireType; |
michael@0 | 733 | let expireTime; |
michael@0 | 734 | let histogram = |
michael@0 | 735 | Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION"); |
michael@0 | 736 | |
michael@0 | 737 | // Update the permission manager. |
michael@0 | 738 | // Also update the current state of pluginInfo.fallbackType so that |
michael@0 | 739 | // subsequent opening of the notification shows the current state. |
michael@0 | 740 | switch (aNewState) { |
michael@0 | 741 | case "allownow": |
michael@0 | 742 | permission = Ci.nsIPermissionManager.ALLOW_ACTION; |
michael@0 | 743 | expireType = Ci.nsIPermissionManager.EXPIRE_SESSION; |
michael@0 | 744 | expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000; |
michael@0 | 745 | histogram.add(0); |
michael@0 | 746 | aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; |
michael@0 | 747 | break; |
michael@0 | 748 | |
michael@0 | 749 | case "allowalways": |
michael@0 | 750 | permission = Ci.nsIPermissionManager.ALLOW_ACTION; |
michael@0 | 751 | expireType = Ci.nsIPermissionManager.EXPIRE_TIME; |
michael@0 | 752 | expireTime = Date.now() + |
michael@0 | 753 | Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000; |
michael@0 | 754 | histogram.add(1); |
michael@0 | 755 | aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; |
michael@0 | 756 | break; |
michael@0 | 757 | |
michael@0 | 758 | case "block": |
michael@0 | 759 | permission = Ci.nsIPermissionManager.PROMPT_ACTION; |
michael@0 | 760 | expireType = Ci.nsIPermissionManager.EXPIRE_NEVER; |
michael@0 | 761 | expireTime = 0; |
michael@0 | 762 | histogram.add(2); |
michael@0 | 763 | switch (aPluginInfo.blocklistState) { |
michael@0 | 764 | case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE: |
michael@0 | 765 | aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE; |
michael@0 | 766 | break; |
michael@0 | 767 | case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE: |
michael@0 | 768 | aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; |
michael@0 | 769 | break; |
michael@0 | 770 | default: |
michael@0 | 771 | aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY; |
michael@0 | 772 | } |
michael@0 | 773 | break; |
michael@0 | 774 | |
michael@0 | 775 | // In case a plugin has already been allowed in another tab, the "continue allowing" button |
michael@0 | 776 | // shouldn't change any permissions but should run the plugin-enablement code below. |
michael@0 | 777 | case "continue": |
michael@0 | 778 | aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; |
michael@0 | 779 | break; |
michael@0 | 780 | default: |
michael@0 | 781 | Cu.reportError(Error("Unexpected plugin state: " + aNewState)); |
michael@0 | 782 | return; |
michael@0 | 783 | } |
michael@0 | 784 | |
michael@0 | 785 | let browser = aNotification.browser; |
michael@0 | 786 | let contentWindow = browser.contentWindow; |
michael@0 | 787 | if (aNewState != "continue") { |
michael@0 | 788 | let principal = contentWindow.document.nodePrincipal; |
michael@0 | 789 | Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString, |
michael@0 | 790 | permission, expireType, expireTime); |
michael@0 | 791 | aPluginInfo.pluginPermissionType = expireType; |
michael@0 | 792 | } |
michael@0 | 793 | |
michael@0 | 794 | // Manually activate the plugins that would have been automatically |
michael@0 | 795 | // activated. |
michael@0 | 796 | let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 797 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 798 | let plugins = cwu.plugins; |
michael@0 | 799 | let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
michael@0 | 800 | |
michael@0 | 801 | let pluginFound = false; |
michael@0 | 802 | for (let plugin of plugins) { |
michael@0 | 803 | plugin.QueryInterface(Ci.nsIObjectLoadingContent); |
michael@0 | 804 | if (!gPluginHandler.isKnownPlugin(plugin)) { |
michael@0 | 805 | continue; |
michael@0 | 806 | } |
michael@0 | 807 | if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { |
michael@0 | 808 | pluginFound = true; |
michael@0 | 809 | if (aNewState == "block") { |
michael@0 | 810 | plugin.reload(true); |
michael@0 | 811 | } else { |
michael@0 | 812 | if (gPluginHandler.canActivatePlugin(plugin)) { |
michael@0 | 813 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 814 | if (overlay) { |
michael@0 | 815 | overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); |
michael@0 | 816 | } |
michael@0 | 817 | plugin.playPlugin(); |
michael@0 | 818 | } |
michael@0 | 819 | } |
michael@0 | 820 | } |
michael@0 | 821 | } |
michael@0 | 822 | |
michael@0 | 823 | // If there are no instances of the plugin on the page any more, what the |
michael@0 | 824 | // user probably needs is for us to allow and then refresh. |
michael@0 | 825 | if (aNewState != "block" && !pluginFound) { |
michael@0 | 826 | browser.reload(); |
michael@0 | 827 | } |
michael@0 | 828 | |
michael@0 | 829 | this._setPluginNotificationIcon(browser); |
michael@0 | 830 | }, |
michael@0 | 831 | |
michael@0 | 832 | _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) { |
michael@0 | 833 | let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); |
michael@0 | 834 | let plugins = []; |
michael@0 | 835 | |
michael@0 | 836 | // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need |
michael@0 | 837 | // to collect all the plugins |
michael@0 | 838 | if (aPlugin === null) { |
michael@0 | 839 | let contentWindow = aBrowser.contentWindow; |
michael@0 | 840 | let contentDoc = aBrowser.contentDocument; |
michael@0 | 841 | let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 842 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 843 | // cwu.plugins may contain non-plugin <object>s, filter them out |
michael@0 | 844 | plugins = cwu.plugins.filter((plugin) => |
michael@0 | 845 | plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); |
michael@0 | 846 | |
michael@0 | 847 | if (plugins.length == 0) { |
michael@0 | 848 | if (notification) { |
michael@0 | 849 | PopupNotifications.remove(notification); |
michael@0 | 850 | } |
michael@0 | 851 | return; |
michael@0 | 852 | } |
michael@0 | 853 | } else { |
michael@0 | 854 | plugins = [aPlugin]; |
michael@0 | 855 | } |
michael@0 | 856 | |
michael@0 | 857 | // If this is a new notification, create a pluginData map, otherwise append |
michael@0 | 858 | let pluginData; |
michael@0 | 859 | if (notification) { |
michael@0 | 860 | pluginData = notification.options.pluginData; |
michael@0 | 861 | } else { |
michael@0 | 862 | pluginData = new Map(); |
michael@0 | 863 | } |
michael@0 | 864 | |
michael@0 | 865 | let principal = aBrowser.contentDocument.nodePrincipal; |
michael@0 | 866 | let principalHost = this._getHostFromPrincipal(principal); |
michael@0 | 867 | |
michael@0 | 868 | for (var plugin of plugins) { |
michael@0 | 869 | let pluginInfo = this._getPluginInfo(plugin); |
michael@0 | 870 | if (pluginInfo.permissionString === null) { |
michael@0 | 871 | Cu.reportError("No permission string for active plugin."); |
michael@0 | 872 | continue; |
michael@0 | 873 | } |
michael@0 | 874 | if (pluginData.has(pluginInfo.permissionString)) { |
michael@0 | 875 | continue; |
michael@0 | 876 | } |
michael@0 | 877 | |
michael@0 | 878 | let permissionObj = Services.perms. |
michael@0 | 879 | getPermissionObject(principal, pluginInfo.permissionString, false); |
michael@0 | 880 | if (permissionObj) { |
michael@0 | 881 | pluginInfo.pluginPermissionHost = permissionObj.host; |
michael@0 | 882 | pluginInfo.pluginPermissionType = permissionObj.expireType; |
michael@0 | 883 | } |
michael@0 | 884 | else { |
michael@0 | 885 | pluginInfo.pluginPermissionHost = principalHost; |
michael@0 | 886 | pluginInfo.pluginPermissionType = undefined; |
michael@0 | 887 | } |
michael@0 | 888 | |
michael@0 | 889 | let url; |
michael@0 | 890 | // TODO: allow the blocklist to specify a better link, bug 873093 |
michael@0 | 891 | if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { |
michael@0 | 892 | url = Services.urlFormatter.formatURLPref("plugins.update.url"); |
michael@0 | 893 | } |
michael@0 | 894 | else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { |
michael@0 | 895 | url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag); |
michael@0 | 896 | } |
michael@0 | 897 | else { |
michael@0 | 898 | url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay"; |
michael@0 | 899 | } |
michael@0 | 900 | pluginInfo.detailsLink = url; |
michael@0 | 901 | |
michael@0 | 902 | pluginData.set(pluginInfo.permissionString, pluginInfo); |
michael@0 | 903 | } |
michael@0 | 904 | |
michael@0 | 905 | let primaryPluginPermission = null; |
michael@0 | 906 | if (aShowNow) { |
michael@0 | 907 | primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString; |
michael@0 | 908 | } |
michael@0 | 909 | |
michael@0 | 910 | if (notification) { |
michael@0 | 911 | // Don't modify the notification UI while it's on the screen, that would be |
michael@0 | 912 | // jumpy and might allow clickjacking. |
michael@0 | 913 | if (aShowNow) { |
michael@0 | 914 | notification.options.primaryPlugin = primaryPluginPermission; |
michael@0 | 915 | notification.reshow(); |
michael@0 | 916 | setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); |
michael@0 | 917 | } |
michael@0 | 918 | return; |
michael@0 | 919 | } |
michael@0 | 920 | |
michael@0 | 921 | let options = { |
michael@0 | 922 | dismissed: !aShowNow, |
michael@0 | 923 | eventCallback: this._clickToPlayNotificationEventCallback, |
michael@0 | 924 | primaryPlugin: primaryPluginPermission, |
michael@0 | 925 | pluginData: pluginData |
michael@0 | 926 | }; |
michael@0 | 927 | PopupNotifications.show(aBrowser, "click-to-play-plugins", |
michael@0 | 928 | "", "plugins-notification-icon", |
michael@0 | 929 | null, null, options); |
michael@0 | 930 | setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); |
michael@0 | 931 | }, |
michael@0 | 932 | |
michael@0 | 933 | _setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) { |
michael@0 | 934 | // Because this is called on a timeout, sanity-check before continuing |
michael@0 | 935 | if (!aBrowser.docShell || !aBrowser.contentWindow) { |
michael@0 | 936 | return; |
michael@0 | 937 | } |
michael@0 | 938 | |
michael@0 | 939 | let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); |
michael@0 | 940 | if (!notification) |
michael@0 | 941 | return; |
michael@0 | 942 | |
michael@0 | 943 | // Make a copy of the actions, removing active plugins and checking for |
michael@0 | 944 | // outdated plugins. |
michael@0 | 945 | let haveInsecure = false; |
michael@0 | 946 | let actions = new Map(); |
michael@0 | 947 | for (let action of notification.options.pluginData.values()) { |
michael@0 | 948 | switch (action.fallbackType) { |
michael@0 | 949 | // haveInsecure will trigger the red flashing icon and the infobar |
michael@0 | 950 | // styling below |
michael@0 | 951 | case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: |
michael@0 | 952 | case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: |
michael@0 | 953 | haveInsecure = true; |
michael@0 | 954 | // fall through |
michael@0 | 955 | |
michael@0 | 956 | case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: |
michael@0 | 957 | actions.set(action.permissionString, action); |
michael@0 | 958 | continue; |
michael@0 | 959 | } |
michael@0 | 960 | } |
michael@0 | 961 | |
michael@0 | 962 | // check for hidden plugins |
michael@0 | 963 | let contentWindow = aBrowser.contentWindow; |
michael@0 | 964 | let contentDoc = aBrowser.contentDocument; |
michael@0 | 965 | let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 966 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 967 | for (let plugin of cwu.plugins) { |
michael@0 | 968 | let info = this._getPluginInfo(plugin); |
michael@0 | 969 | if (!actions.has(info.permissionString)) { |
michael@0 | 970 | continue; |
michael@0 | 971 | } |
michael@0 | 972 | let fallbackType = info.fallbackType; |
michael@0 | 973 | if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) { |
michael@0 | 974 | actions.delete(info.permissionString); |
michael@0 | 975 | if (actions.size == 0) { |
michael@0 | 976 | break; |
michael@0 | 977 | } |
michael@0 | 978 | continue; |
michael@0 | 979 | } |
michael@0 | 980 | if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && |
michael@0 | 981 | fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE && |
michael@0 | 982 | fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) { |
michael@0 | 983 | continue; |
michael@0 | 984 | } |
michael@0 | 985 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 986 | if (!overlay) { |
michael@0 | 987 | continue; |
michael@0 | 988 | } |
michael@0 | 989 | let shouldShow = this.shouldShowOverlay(plugin, overlay); |
michael@0 | 990 | this.setVisibility(plugin, overlay, shouldShow); |
michael@0 | 991 | if (shouldShow) { |
michael@0 | 992 | actions.delete(info.permissionString); |
michael@0 | 993 | if (actions.size == 0) { |
michael@0 | 994 | break; |
michael@0 | 995 | } |
michael@0 | 996 | } |
michael@0 | 997 | } |
michael@0 | 998 | |
michael@0 | 999 | // Set up the icon |
michael@0 | 1000 | document.getElementById("plugins-notification-icon").classList. |
michael@0 | 1001 | toggle("plugin-blocked", haveInsecure); |
michael@0 | 1002 | |
michael@0 | 1003 | // Now configure the notification bar |
michael@0 | 1004 | |
michael@0 | 1005 | let notificationBox = gBrowser.getNotificationBox(aBrowser); |
michael@0 | 1006 | |
michael@0 | 1007 | function hideNotification() { |
michael@0 | 1008 | let n = notificationBox.getNotificationWithValue("plugin-hidden"); |
michael@0 | 1009 | if (n) { |
michael@0 | 1010 | notificationBox.removeNotification(n, true); |
michael@0 | 1011 | } |
michael@0 | 1012 | } |
michael@0 | 1013 | |
michael@0 | 1014 | // There are three different cases when showing an infobar: |
michael@0 | 1015 | // 1. A single type of plugin is hidden on the page. Show the UI for that |
michael@0 | 1016 | // plugin. |
michael@0 | 1017 | // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI |
michael@0 | 1018 | // with the vulnerable styling. |
michael@0 | 1019 | // 2b. Multiple types of plugins are hidden on the page, but none are |
michael@0 | 1020 | // vulnerable. Show the nonvulnerable multi-UI. |
michael@0 | 1021 | function showNotification() { |
michael@0 | 1022 | let n = notificationBox.getNotificationWithValue("plugin-hidden"); |
michael@0 | 1023 | if (n) { |
michael@0 | 1024 | // If something is already shown, just keep it |
michael@0 | 1025 | return; |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN"). |
michael@0 | 1029 | add(true); |
michael@0 | 1030 | |
michael@0 | 1031 | let message; |
michael@0 | 1032 | // Icons set directly cannot be manipulated using moz-image-region, so |
michael@0 | 1033 | // we use CSS classes instead. |
michael@0 | 1034 | let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal); |
michael@0 | 1035 | let brand = document.getElementById("bundle_brand").getString("brandShortName"); |
michael@0 | 1036 | |
michael@0 | 1037 | if (actions.size == 1) { |
michael@0 | 1038 | let pluginInfo = [...actions.values()][0]; |
michael@0 | 1039 | let pluginName = pluginInfo.pluginName; |
michael@0 | 1040 | |
michael@0 | 1041 | switch (pluginInfo.fallbackType) { |
michael@0 | 1042 | case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: |
michael@0 | 1043 | message = gNavigatorBundle.getFormattedString( |
michael@0 | 1044 | "pluginActivateNew.message", |
michael@0 | 1045 | [pluginName, host]); |
michael@0 | 1046 | break; |
michael@0 | 1047 | case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: |
michael@0 | 1048 | message = gNavigatorBundle.getFormattedString( |
michael@0 | 1049 | "pluginActivateOutdated.message", |
michael@0 | 1050 | [pluginName, host, brand]); |
michael@0 | 1051 | break; |
michael@0 | 1052 | case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: |
michael@0 | 1053 | message = gNavigatorBundle.getFormattedString( |
michael@0 | 1054 | "pluginActivateVulnerable.message", |
michael@0 | 1055 | [pluginName, host, brand]); |
michael@0 | 1056 | } |
michael@0 | 1057 | } else { |
michael@0 | 1058 | // Multi-plugin |
michael@0 | 1059 | message = gNavigatorBundle.getFormattedString( |
michael@0 | 1060 | "pluginActivateMultiple.message", [host]); |
michael@0 | 1061 | |
michael@0 | 1062 | for (let action of actions.values()) { |
michael@0 | 1063 | if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) { |
michael@0 | 1064 | break; |
michael@0 | 1065 | } |
michael@0 | 1066 | } |
michael@0 | 1067 | } |
michael@0 | 1068 | |
michael@0 | 1069 | let buttons = [ |
michael@0 | 1070 | { |
michael@0 | 1071 | label: gNavigatorBundle.getString("pluginContinueBlocking.label"), |
michael@0 | 1072 | accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"), |
michael@0 | 1073 | callback: function() { |
michael@0 | 1074 | Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK"). |
michael@0 | 1075 | add(true); |
michael@0 | 1076 | |
michael@0 | 1077 | Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal, |
michael@0 | 1078 | "plugin-hidden-notification", |
michael@0 | 1079 | Services.perms.DENY_ACTION); |
michael@0 | 1080 | } |
michael@0 | 1081 | }, |
michael@0 | 1082 | { |
michael@0 | 1083 | label: gNavigatorBundle.getString("pluginActivateTrigger.label"), |
michael@0 | 1084 | accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"), |
michael@0 | 1085 | callback: function() { |
michael@0 | 1086 | Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW"). |
michael@0 | 1087 | add(true); |
michael@0 | 1088 | |
michael@0 | 1089 | let curNotification = |
michael@0 | 1090 | PopupNotifications.getNotification("click-to-play-plugins", |
michael@0 | 1091 | aBrowser); |
michael@0 | 1092 | if (curNotification) { |
michael@0 | 1093 | curNotification.reshow(); |
michael@0 | 1094 | } |
michael@0 | 1095 | } |
michael@0 | 1096 | } |
michael@0 | 1097 | ]; |
michael@0 | 1098 | n = notificationBox. |
michael@0 | 1099 | appendNotification(message, "plugin-hidden", null, |
michael@0 | 1100 | notificationBox.PRIORITY_INFO_HIGH, buttons); |
michael@0 | 1101 | if (haveInsecure) { |
michael@0 | 1102 | n.classList.add('pluginVulnerable'); |
michael@0 | 1103 | } |
michael@0 | 1104 | } |
michael@0 | 1105 | |
michael@0 | 1106 | if (actions.size == 0) { |
michael@0 | 1107 | hideNotification(); |
michael@0 | 1108 | } else { |
michael@0 | 1109 | let notificationPermission = Services.perms.testPermissionFromPrincipal( |
michael@0 | 1110 | aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification"); |
michael@0 | 1111 | if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) { |
michael@0 | 1112 | hideNotification(); |
michael@0 | 1113 | } else { |
michael@0 | 1114 | showNotification(); |
michael@0 | 1115 | } |
michael@0 | 1116 | } |
michael@0 | 1117 | }, |
michael@0 | 1118 | |
michael@0 | 1119 | // Crashed-plugin observer. Notified once per plugin crash, before events |
michael@0 | 1120 | // are dispatched to individual plugin instances. |
michael@0 | 1121 | pluginCrashed : function(subject, topic, data) { |
michael@0 | 1122 | let propertyBag = subject; |
michael@0 | 1123 | if (!(propertyBag instanceof Ci.nsIPropertyBag2) || |
michael@0 | 1124 | !(propertyBag instanceof Ci.nsIWritablePropertyBag2)) |
michael@0 | 1125 | return; |
michael@0 | 1126 | |
michael@0 | 1127 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 1128 | let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID"); |
michael@0 | 1129 | let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID"); |
michael@0 | 1130 | let shouldSubmit = gCrashReporter.submitReports; |
michael@0 | 1131 | let doPrompt = true; // XXX followup to get via gCrashReporter |
michael@0 | 1132 | |
michael@0 | 1133 | // Submit automatically when appropriate. |
michael@0 | 1134 | if (pluginDumpID && shouldSubmit && !doPrompt) { |
michael@0 | 1135 | this.submitReport(pluginDumpID, browserDumpID); |
michael@0 | 1136 | // Submission is async, so we can't easily show failure UI. |
michael@0 | 1137 | propertyBag.setPropertyAsBool("submittedCrashReport", true); |
michael@0 | 1138 | } |
michael@0 | 1139 | #endif |
michael@0 | 1140 | }, |
michael@0 | 1141 | |
michael@0 | 1142 | // Crashed-plugin event listener. Called for every instance of a |
michael@0 | 1143 | // plugin in content. |
michael@0 | 1144 | pluginInstanceCrashed: function (plugin, aEvent) { |
michael@0 | 1145 | // Ensure the plugin and event are of the right type. |
michael@0 | 1146 | if (!(aEvent instanceof Ci.nsIDOMCustomEvent)) |
michael@0 | 1147 | return; |
michael@0 | 1148 | |
michael@0 | 1149 | let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); |
michael@0 | 1150 | let submittedReport = propBag.getPropertyAsBool("submittedCrashReport"); |
michael@0 | 1151 | let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt"); |
michael@0 | 1152 | let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports"); |
michael@0 | 1153 | let pluginName = propBag.getPropertyAsAString("pluginName"); |
michael@0 | 1154 | let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID"); |
michael@0 | 1155 | let browserDumpID = propBag.getPropertyAsAString("browserDumpID"); |
michael@0 | 1156 | |
michael@0 | 1157 | // Remap the plugin name to a more user-presentable form. |
michael@0 | 1158 | pluginName = this.makeNicePluginName(pluginName); |
michael@0 | 1159 | |
michael@0 | 1160 | let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); |
michael@0 | 1161 | |
michael@0 | 1162 | // |
michael@0 | 1163 | // Configure the crashed-plugin placeholder. |
michael@0 | 1164 | // |
michael@0 | 1165 | |
michael@0 | 1166 | // Force a layout flush so the binding is attached. |
michael@0 | 1167 | plugin.clientTop; |
michael@0 | 1168 | let overlay = this.getPluginUI(plugin, "main"); |
michael@0 | 1169 | let statusDiv = this.getPluginUI(plugin, "submitStatus"); |
michael@0 | 1170 | let doc = plugin.ownerDocument; |
michael@0 | 1171 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 1172 | let status; |
michael@0 | 1173 | |
michael@0 | 1174 | // Determine which message to show regarding crash reports. |
michael@0 | 1175 | if (submittedReport) { // submitReports && !doPrompt, handled in observer |
michael@0 | 1176 | status = "submitted"; |
michael@0 | 1177 | } |
michael@0 | 1178 | else if (!submitReports && !doPrompt) { |
michael@0 | 1179 | status = "noSubmit"; |
michael@0 | 1180 | } |
michael@0 | 1181 | else { // doPrompt |
michael@0 | 1182 | status = "please"; |
michael@0 | 1183 | this.getPluginUI(plugin, "submitButton").addEventListener("click", |
michael@0 | 1184 | function (event) { |
michael@0 | 1185 | if (event.button != 0 || !event.isTrusted) |
michael@0 | 1186 | return; |
michael@0 | 1187 | this.submitReport(pluginDumpID, browserDumpID, plugin); |
michael@0 | 1188 | pref.setBoolPref("", optInCB.checked); |
michael@0 | 1189 | }.bind(this)); |
michael@0 | 1190 | let optInCB = this.getPluginUI(plugin, "submitURLOptIn"); |
michael@0 | 1191 | let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL"); |
michael@0 | 1192 | optInCB.checked = pref.getBoolPref(""); |
michael@0 | 1193 | } |
michael@0 | 1194 | |
michael@0 | 1195 | // If we don't have a minidumpID, we can't (or didn't) submit anything. |
michael@0 | 1196 | // This can happen if the plugin is killed from the task manager. |
michael@0 | 1197 | if (!pluginDumpID) { |
michael@0 | 1198 | status = "noReport"; |
michael@0 | 1199 | } |
michael@0 | 1200 | |
michael@0 | 1201 | statusDiv.setAttribute("status", status); |
michael@0 | 1202 | |
michael@0 | 1203 | let helpIcon = this.getPluginUI(plugin, "helpIcon"); |
michael@0 | 1204 | this.addLinkClickCallback(helpIcon, "openHelpPage"); |
michael@0 | 1205 | |
michael@0 | 1206 | // If we're showing the link to manually trigger report submission, we'll |
michael@0 | 1207 | // want to be able to update all the instances of the UI for this crash to |
michael@0 | 1208 | // show an updated message when a report is submitted. |
michael@0 | 1209 | if (doPrompt) { |
michael@0 | 1210 | let observer = { |
michael@0 | 1211 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, |
michael@0 | 1212 | Ci.nsISupportsWeakReference]), |
michael@0 | 1213 | observe : function(subject, topic, data) { |
michael@0 | 1214 | let propertyBag = subject; |
michael@0 | 1215 | if (!(propertyBag instanceof Ci.nsIPropertyBag2)) |
michael@0 | 1216 | return; |
michael@0 | 1217 | // Ignore notifications for other crashes. |
michael@0 | 1218 | if (propertyBag.get("minidumpID") != pluginDumpID) |
michael@0 | 1219 | return; |
michael@0 | 1220 | statusDiv.setAttribute("status", data); |
michael@0 | 1221 | }, |
michael@0 | 1222 | |
michael@0 | 1223 | handleEvent : function(event) { |
michael@0 | 1224 | // Not expected to be called, just here for the closure. |
michael@0 | 1225 | } |
michael@0 | 1226 | } |
michael@0 | 1227 | |
michael@0 | 1228 | // Use a weak reference, so we don't have to remove it... |
michael@0 | 1229 | Services.obs.addObserver(observer, "crash-report-status", true); |
michael@0 | 1230 | // ...alas, now we need something to hold a strong reference to prevent |
michael@0 | 1231 | // it from being GC. But I don't want to manually manage the reference's |
michael@0 | 1232 | // lifetime (which should be no greater than the page). |
michael@0 | 1233 | // Clever solution? Use a closue with an event listener on the document. |
michael@0 | 1234 | // When the doc goes away, so do the listener references and the closure. |
michael@0 | 1235 | doc.addEventListener("mozCleverClosureHack", observer, false); |
michael@0 | 1236 | } |
michael@0 | 1237 | #endif |
michael@0 | 1238 | |
michael@0 | 1239 | let crashText = this.getPluginUI(plugin, "crashedText"); |
michael@0 | 1240 | crashText.textContent = messageString; |
michael@0 | 1241 | |
michael@0 | 1242 | let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
michael@0 | 1243 | |
michael@0 | 1244 | let link = this.getPluginUI(plugin, "reloadLink"); |
michael@0 | 1245 | this.addLinkClickCallback(link, "reloadPage", browser); |
michael@0 | 1246 | |
michael@0 | 1247 | let notificationBox = gBrowser.getNotificationBox(browser); |
michael@0 | 1248 | |
michael@0 | 1249 | let isShowing = this.shouldShowOverlay(plugin, overlay); |
michael@0 | 1250 | |
michael@0 | 1251 | // Is the <object>'s size too small to hold what we want to show? |
michael@0 | 1252 | if (!isShowing) { |
michael@0 | 1253 | // First try hiding the crash report submission UI. |
michael@0 | 1254 | statusDiv.removeAttribute("status"); |
michael@0 | 1255 | |
michael@0 | 1256 | isShowing = this.shouldShowOverlay(plugin, overlay); |
michael@0 | 1257 | } |
michael@0 | 1258 | this.setVisibility(plugin, overlay, isShowing); |
michael@0 | 1259 | |
michael@0 | 1260 | if (isShowing) { |
michael@0 | 1261 | // If a previous plugin on the page was too small and resulted in adding a |
michael@0 | 1262 | // notification bar, then remove it because this plugin instance it big |
michael@0 | 1263 | // enough to serve as in-content notification. |
michael@0 | 1264 | hideNotificationBar(); |
michael@0 | 1265 | doc.mozNoPluginCrashedNotification = true; |
michael@0 | 1266 | } else { |
michael@0 | 1267 | // If another plugin on the page was large enough to show our UI, we don't |
michael@0 | 1268 | // want to show a notification bar. |
michael@0 | 1269 | if (!doc.mozNoPluginCrashedNotification) |
michael@0 | 1270 | showNotificationBar(pluginDumpID, browserDumpID); |
michael@0 | 1271 | } |
michael@0 | 1272 | |
michael@0 | 1273 | function hideNotificationBar() { |
michael@0 | 1274 | let notification = notificationBox.getNotificationWithValue("plugin-crashed"); |
michael@0 | 1275 | if (notification) |
michael@0 | 1276 | notificationBox.removeNotification(notification, true); |
michael@0 | 1277 | } |
michael@0 | 1278 | |
michael@0 | 1279 | function showNotificationBar(pluginDumpID, browserDumpID) { |
michael@0 | 1280 | // If there's already an existing notification bar, don't do anything. |
michael@0 | 1281 | let notification = notificationBox.getNotificationWithValue("plugin-crashed"); |
michael@0 | 1282 | if (notification) |
michael@0 | 1283 | return; |
michael@0 | 1284 | |
michael@0 | 1285 | // Configure the notification bar |
michael@0 | 1286 | let priority = notificationBox.PRIORITY_WARNING_MEDIUM; |
michael@0 | 1287 | let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png"; |
michael@0 | 1288 | let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label"); |
michael@0 | 1289 | let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey"); |
michael@0 | 1290 | let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label"); |
michael@0 | 1291 | let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey"); |
michael@0 | 1292 | |
michael@0 | 1293 | let buttons = [{ |
michael@0 | 1294 | label: reloadLabel, |
michael@0 | 1295 | accessKey: reloadKey, |
michael@0 | 1296 | popup: null, |
michael@0 | 1297 | callback: function() { browser.reload(); }, |
michael@0 | 1298 | }]; |
michael@0 | 1299 | #ifdef MOZ_CRASHREPORTER |
michael@0 | 1300 | let submitButton = { |
michael@0 | 1301 | label: submitLabel, |
michael@0 | 1302 | accessKey: submitKey, |
michael@0 | 1303 | popup: null, |
michael@0 | 1304 | callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); }, |
michael@0 | 1305 | }; |
michael@0 | 1306 | if (pluginDumpID) |
michael@0 | 1307 | buttons.push(submitButton); |
michael@0 | 1308 | #endif |
michael@0 | 1309 | |
michael@0 | 1310 | let notification = notificationBox.appendNotification(messageString, "plugin-crashed", |
michael@0 | 1311 | iconURL, priority, buttons); |
michael@0 | 1312 | |
michael@0 | 1313 | // Add the "learn more" link. |
michael@0 | 1314 | let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
michael@0 | 1315 | let link = notification.ownerDocument.createElementNS(XULNS, "label"); |
michael@0 | 1316 | link.className = "text-link"; |
michael@0 | 1317 | link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore")); |
michael@0 | 1318 | let crashurl = formatURL("app.support.baseURL", true); |
michael@0 | 1319 | crashurl += "plugin-crashed-notificationbar"; |
michael@0 | 1320 | link.href = crashurl; |
michael@0 | 1321 | |
michael@0 | 1322 | let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); |
michael@0 | 1323 | description.appendChild(link); |
michael@0 | 1324 | |
michael@0 | 1325 | // Remove the notfication when the page is reloaded. |
michael@0 | 1326 | doc.defaultView.top.addEventListener("unload", function() { |
michael@0 | 1327 | notificationBox.removeNotification(notification); |
michael@0 | 1328 | }, false); |
michael@0 | 1329 | } |
michael@0 | 1330 | |
michael@0 | 1331 | } |
michael@0 | 1332 | }; |