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