|
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/. |
|
5 |
|
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", |
|
11 |
|
12 getPluginUI: function (plugin, anonid) { |
|
13 return plugin.ownerDocument. |
|
14 getAnonymousElementByAttribute(plugin, "anonid", anonid); |
|
15 }, |
|
16 |
|
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 |
|
24 |
|
25 _getPluginInfo: function (pluginElement) { |
|
26 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
|
27 pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); |
|
28 |
|
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; |
|
35 |
|
36 tagMimetype = pluginElement.actualType; |
|
37 if (tagMimetype == "") { |
|
38 tagMimetype = pluginElement.type; |
|
39 } |
|
40 |
|
41 if (gPluginHandler.isKnownPlugin(pluginElement)) { |
|
42 pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); |
|
43 pluginName = gPluginHandler.makeNicePluginName(pluginTag.name); |
|
44 |
|
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 } |
|
56 |
|
57 return { mimetype: tagMimetype, |
|
58 pluginName: pluginName, |
|
59 pluginTag: pluginTag, |
|
60 permissionString: permissionString, |
|
61 fallbackType: fallbackType, |
|
62 blocklistState: blocklistState, |
|
63 }; |
|
64 }, |
|
65 |
|
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"; |
|
70 |
|
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 }, |
|
82 |
|
83 /** |
|
84 * Update the visibility of the plugin overlay. |
|
85 */ |
|
86 setVisibility : function (plugin, overlay, shouldShow) { |
|
87 overlay.classList.toggle("visible", shouldShow); |
|
88 }, |
|
89 |
|
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 } |
|
104 |
|
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 } |
|
114 |
|
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]]; |
|
128 |
|
129 if (right <= 0 || top <= 0) { |
|
130 return false; |
|
131 } |
|
132 |
|
133 let contentWindow = plugin.ownerDocument.defaultView; |
|
134 let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
135 .getInterface(Ci.nsIDOMWindowUtils); |
|
136 |
|
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 } |
|
143 |
|
144 return true; |
|
145 }, |
|
146 |
|
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); |
|
161 |
|
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 }, |
|
176 |
|
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; |
|
181 |
|
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 }, |
|
204 |
|
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 }, |
|
217 |
|
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 }, |
|
248 |
|
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 }, |
|
258 |
|
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 }, |
|
267 |
|
268 handleEvent : function(event) { |
|
269 let eventType = event.type; |
|
270 |
|
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 } |
|
278 |
|
279 let plugin = event.target; |
|
280 let doc = plugin.ownerDocument; |
|
281 |
|
282 if (!(plugin instanceof Ci.nsIObjectLoadingContent)) |
|
283 return; |
|
284 |
|
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; |
|
294 |
|
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 } |
|
302 |
|
303 let shouldShowNotification = false; |
|
304 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
|
305 if (!browser) |
|
306 return; |
|
307 |
|
308 switch (eventType) { |
|
309 case "PluginCrashed": |
|
310 this.pluginInstanceCrashed(plugin, event); |
|
311 break; |
|
312 |
|
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"); |
|
323 |
|
324 let installLink = this.getPluginUI(plugin, "installPluginLink"); |
|
325 this.addLinkClickCallback(installLink, "installSinglePlugin", plugin); |
|
326 } |
|
327 break; |
|
328 |
|
329 case "PluginBlocklisted": |
|
330 case "PluginOutdated": |
|
331 shouldShowNotification = true; |
|
332 break; |
|
333 |
|
334 case "PluginVulnerableUpdatable": |
|
335 let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink"); |
|
336 this.addLinkClickCallback(updateLink, "openPluginUpdatePage"); |
|
337 /* FALLTHRU */ |
|
338 |
|
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; |
|
355 |
|
356 case "PluginPlayPreview": |
|
357 this._handlePlayPreviewEvent(plugin); |
|
358 break; |
|
359 |
|
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; |
|
366 |
|
367 case "PluginInstantiated": |
|
368 shouldShowNotification = true; |
|
369 break; |
|
370 } |
|
371 |
|
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 } |
|
387 |
|
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 } |
|
395 |
|
396 if (shouldShowNotification) { |
|
397 this._showClickToPlayNotification(browser, plugin, false); |
|
398 } |
|
399 }, |
|
400 |
|
401 isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) { |
|
402 return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == |
|
403 Ci.nsIObjectLoadingContent.TYPE_PLUGIN); |
|
404 }, |
|
405 |
|
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; |
|
412 |
|
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); |
|
417 |
|
418 let isFallbackTypeValid = |
|
419 objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && |
|
420 objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; |
|
421 |
|
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 } |
|
427 |
|
428 return !objLoadingContent.activated && |
|
429 pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && |
|
430 isFallbackTypeValid; |
|
431 }, |
|
432 |
|
433 hideClickToPlayOverlay: function(aPlugin) { |
|
434 let overlay = this.getPluginUI(aPlugin, "main"); |
|
435 if (overlay) { |
|
436 overlay.classList.remove("visible"); |
|
437 } |
|
438 }, |
|
439 |
|
440 stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) { |
|
441 let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); |
|
442 if (objLoadingContent.activated) |
|
443 return; |
|
444 |
|
445 if (aPlayPlugin) |
|
446 objLoadingContent.playPlugin(); |
|
447 else |
|
448 objLoadingContent.cancelPlayPreview(); |
|
449 }, |
|
450 |
|
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; |
|
456 |
|
457 var notificationBox = gBrowser.getNotificationBox(browser); |
|
458 var notification = notificationBox.getNotificationWithValue("missing-plugins"); |
|
459 if (notification) |
|
460 notificationBox.removeNotification(notification); |
|
461 |
|
462 // reload the browser to make the new plugin show. |
|
463 browser.reload(); |
|
464 }, |
|
465 |
|
466 // Callback for user clicking on a missing (unsupported) plugin. |
|
467 installSinglePlugin: function (plugin) { |
|
468 var missingPlugins = new Map(); |
|
469 |
|
470 var pluginInfo = this._getPluginInfo(plugin); |
|
471 missingPlugins.set(pluginInfo.mimetype, pluginInfo); |
|
472 |
|
473 openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", |
|
474 "PFSWindow", "chrome,centerscreen,resizable=yes", |
|
475 {plugins: missingPlugins, browser: gBrowser.selectedBrowser}); |
|
476 }, |
|
477 |
|
478 // Callback for user clicking on a disabled plugin |
|
479 managePlugins: function (aEvent) { |
|
480 BrowserOpenAddonsMgr("addons://list/plugin"); |
|
481 }, |
|
482 |
|
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 }, |
|
488 |
|
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 |
|
504 |
|
505 // Callback for user clicking a "reload page" link |
|
506 reloadPage: function (browser) { |
|
507 browser.reload(); |
|
508 }, |
|
509 |
|
510 // Callback for user clicking the help icon |
|
511 openHelpPage: function () { |
|
512 openHelpLink("plugin-crashed", false); |
|
513 }, |
|
514 |
|
515 showInstallNotification: function (aPlugin) { |
|
516 let hideMissingPluginsNotification = |
|
517 Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION); |
|
518 if (hideMissingPluginsNotification) { |
|
519 return false; |
|
520 } |
|
521 |
|
522 let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument |
|
523 .defaultView.top.document); |
|
524 if (!browser.missingPlugins) |
|
525 browser.missingPlugins = new Map(); |
|
526 |
|
527 let pluginInfo = this._getPluginInfo(aPlugin); |
|
528 browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo); |
|
529 |
|
530 // only show notification for small subset of plugins |
|
531 let mimetype = pluginInfo.mimetype.split(";")[0]; |
|
532 if (!this.canInstallThisMimeType(mimetype)) |
|
533 return false; |
|
534 |
|
535 let pluginIdentifier = this.nameForSupportedPlugin(mimetype); |
|
536 if (!pluginIdentifier) |
|
537 return false; |
|
538 |
|
539 let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName; |
|
540 |
|
541 // don't show several notifications |
|
542 let notification = PopupNotifications.getNotification("plugins-not-found", browser); |
|
543 if (notification) |
|
544 return true; |
|
545 |
|
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 }; |
|
559 |
|
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); |
|
590 |
|
591 let overlay = this.getPluginUI(aPlugin, "main"); |
|
592 |
|
593 if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { |
|
594 if (overlay) { |
|
595 overlay.classList.remove("visible"); |
|
596 } |
|
597 return; |
|
598 } |
|
599 |
|
600 if (overlay) { |
|
601 overlay.addEventListener("click", gPluginHandler._overlayClickListener, true); |
|
602 } |
|
603 }, |
|
604 |
|
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 }, |
|
632 |
|
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); |
|
639 |
|
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); |
|
647 |
|
648 // Force a style flush, so that we ensure our binding is attached. |
|
649 aPlugin.clientTop; |
|
650 } |
|
651 iframe.src = playPreviewInfo.redirectURL; |
|
652 |
|
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; |
|
658 |
|
659 previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); |
|
660 |
|
661 let playPlugin = !aEvent.detail; |
|
662 gPluginHandler.stopPlayPreview(aPlugin, playPlugin); |
|
663 |
|
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); |
|
669 |
|
670 if (!playPreviewInfo.ignoreCTP) { |
|
671 gPluginHandler._showClickToPlayNotification(browser, aPlugin, false); |
|
672 } |
|
673 }, |
|
674 |
|
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 }, |
|
691 |
|
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 }, |
|
710 |
|
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 } |
|
716 |
|
717 try { |
|
718 if (principal.URI.host) |
|
719 return principal.URI.host; |
|
720 } catch (e) {} |
|
721 |
|
722 return principal.origin; |
|
723 }, |
|
724 |
|
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"); |
|
736 |
|
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; |
|
748 |
|
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; |
|
757 |
|
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; |
|
774 |
|
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 } |
|
784 |
|
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 } |
|
793 |
|
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); |
|
800 |
|
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 } |
|
822 |
|
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 } |
|
828 |
|
829 this._setPluginNotificationIcon(browser); |
|
830 }, |
|
831 |
|
832 _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) { |
|
833 let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); |
|
834 let plugins = []; |
|
835 |
|
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); |
|
846 |
|
847 if (plugins.length == 0) { |
|
848 if (notification) { |
|
849 PopupNotifications.remove(notification); |
|
850 } |
|
851 return; |
|
852 } |
|
853 } else { |
|
854 plugins = [aPlugin]; |
|
855 } |
|
856 |
|
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 } |
|
864 |
|
865 let principal = aBrowser.contentDocument.nodePrincipal; |
|
866 let principalHost = this._getHostFromPrincipal(principal); |
|
867 |
|
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 } |
|
877 |
|
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 } |
|
888 |
|
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; |
|
901 |
|
902 pluginData.set(pluginInfo.permissionString, pluginInfo); |
|
903 } |
|
904 |
|
905 let primaryPluginPermission = null; |
|
906 if (aShowNow) { |
|
907 primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString; |
|
908 } |
|
909 |
|
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 } |
|
920 |
|
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 }, |
|
932 |
|
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 } |
|
938 |
|
939 let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); |
|
940 if (!notification) |
|
941 return; |
|
942 |
|
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 |
|
955 |
|
956 case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: |
|
957 actions.set(action.permissionString, action); |
|
958 continue; |
|
959 } |
|
960 } |
|
961 |
|
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 } |
|
998 |
|
999 // Set up the icon |
|
1000 document.getElementById("plugins-notification-icon").classList. |
|
1001 toggle("plugin-blocked", haveInsecure); |
|
1002 |
|
1003 // Now configure the notification bar |
|
1004 |
|
1005 let notificationBox = gBrowser.getNotificationBox(aBrowser); |
|
1006 |
|
1007 function hideNotification() { |
|
1008 let n = notificationBox.getNotificationWithValue("plugin-hidden"); |
|
1009 if (n) { |
|
1010 notificationBox.removeNotification(n, true); |
|
1011 } |
|
1012 } |
|
1013 |
|
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 } |
|
1027 |
|
1028 Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN"). |
|
1029 add(true); |
|
1030 |
|
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"); |
|
1036 |
|
1037 if (actions.size == 1) { |
|
1038 let pluginInfo = [...actions.values()][0]; |
|
1039 let pluginName = pluginInfo.pluginName; |
|
1040 |
|
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]); |
|
1061 |
|
1062 for (let action of actions.values()) { |
|
1063 if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) { |
|
1064 break; |
|
1065 } |
|
1066 } |
|
1067 } |
|
1068 |
|
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); |
|
1076 |
|
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); |
|
1088 |
|
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 } |
|
1105 |
|
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 }, |
|
1118 |
|
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; |
|
1126 |
|
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 |
|
1132 |
|
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 }, |
|
1141 |
|
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; |
|
1148 |
|
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"); |
|
1156 |
|
1157 // Remap the plugin name to a more user-presentable form. |
|
1158 pluginName = this.makeNicePluginName(pluginName); |
|
1159 |
|
1160 let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); |
|
1161 |
|
1162 // |
|
1163 // Configure the crashed-plugin placeholder. |
|
1164 // |
|
1165 |
|
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; |
|
1173 |
|
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 } |
|
1194 |
|
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 } |
|
1200 |
|
1201 statusDiv.setAttribute("status", status); |
|
1202 |
|
1203 let helpIcon = this.getPluginUI(plugin, "helpIcon"); |
|
1204 this.addLinkClickCallback(helpIcon, "openHelpPage"); |
|
1205 |
|
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 }, |
|
1222 |
|
1223 handleEvent : function(event) { |
|
1224 // Not expected to be called, just here for the closure. |
|
1225 } |
|
1226 } |
|
1227 |
|
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 |
|
1238 |
|
1239 let crashText = this.getPluginUI(plugin, "crashedText"); |
|
1240 crashText.textContent = messageString; |
|
1241 |
|
1242 let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); |
|
1243 |
|
1244 let link = this.getPluginUI(plugin, "reloadLink"); |
|
1245 this.addLinkClickCallback(link, "reloadPage", browser); |
|
1246 |
|
1247 let notificationBox = gBrowser.getNotificationBox(browser); |
|
1248 |
|
1249 let isShowing = this.shouldShowOverlay(plugin, overlay); |
|
1250 |
|
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"); |
|
1255 |
|
1256 isShowing = this.shouldShowOverlay(plugin, overlay); |
|
1257 } |
|
1258 this.setVisibility(plugin, overlay, isShowing); |
|
1259 |
|
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 } |
|
1272 |
|
1273 function hideNotificationBar() { |
|
1274 let notification = notificationBox.getNotificationWithValue("plugin-crashed"); |
|
1275 if (notification) |
|
1276 notificationBox.removeNotification(notification, true); |
|
1277 } |
|
1278 |
|
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; |
|
1284 |
|
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"); |
|
1292 |
|
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 |
|
1309 |
|
1310 let notification = notificationBox.appendNotification(messageString, "plugin-crashed", |
|
1311 iconURL, priority, buttons); |
|
1312 |
|
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; |
|
1321 |
|
1322 let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); |
|
1323 description.appendChild(link); |
|
1324 |
|
1325 // Remove the notfication when the page is reloaded. |
|
1326 doc.defaultView.top.addEventListener("unload", function() { |
|
1327 notificationBox.removeNotification(notification); |
|
1328 }, false); |
|
1329 } |
|
1330 |
|
1331 } |
|
1332 }; |