|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 "use strict"; |
|
5 |
|
6 var PluginHelper = { |
|
7 showDoorHanger: function(aTab) { |
|
8 if (!aTab.browser) |
|
9 return; |
|
10 |
|
11 // Even though we may not end up showing a doorhanger, this flag |
|
12 // lets us know that we've tried to show a doorhanger. |
|
13 aTab.shouldShowPluginDoorhanger = false; |
|
14 |
|
15 let uri = aTab.browser.currentURI; |
|
16 |
|
17 // If the user has previously set a plugins permission for this website, |
|
18 // either play or don't play the plugins instead of showing a doorhanger. |
|
19 let permValue = Services.perms.testPermission(uri, "plugins"); |
|
20 if (permValue != Services.perms.UNKNOWN_ACTION) { |
|
21 if (permValue == Services.perms.ALLOW_ACTION) |
|
22 PluginHelper.playAllPlugins(aTab.browser.contentWindow); |
|
23 |
|
24 return; |
|
25 } |
|
26 |
|
27 let message = Strings.browser.formatStringFromName("clickToPlayPlugins.message2", |
|
28 [uri.host], 1); |
|
29 let buttons = [ |
|
30 { |
|
31 label: Strings.browser.GetStringFromName("clickToPlayPlugins.activate"), |
|
32 callback: function(aChecked) { |
|
33 // If the user checked "Don't ask again", make a permanent exception |
|
34 if (aChecked) |
|
35 Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION); |
|
36 |
|
37 PluginHelper.playAllPlugins(aTab.browser.contentWindow); |
|
38 } |
|
39 }, |
|
40 { |
|
41 label: Strings.browser.GetStringFromName("clickToPlayPlugins.dontActivate"), |
|
42 callback: function(aChecked) { |
|
43 // If the user checked "Don't ask again", make a permanent exception |
|
44 if (aChecked) |
|
45 Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.DENY_ACTION); |
|
46 |
|
47 // Other than that, do nothing |
|
48 } |
|
49 } |
|
50 ]; |
|
51 |
|
52 // Add a checkbox with a "Don't ask again" message if the uri contains a |
|
53 // host. Adding a permanent exception will fail if host is not present. |
|
54 let options = uri.host ? { checkbox: Strings.browser.GetStringFromName("clickToPlayPlugins.dontAskAgain") } : {}; |
|
55 |
|
56 NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id, options); |
|
57 }, |
|
58 |
|
59 delayAndShowDoorHanger: function(aTab) { |
|
60 // To avoid showing the doorhanger if there are also visible plugin |
|
61 // overlays on the page, delay showing the doorhanger to check if |
|
62 // visible plugins get added in the near future. |
|
63 if (!aTab.pluginDoorhangerTimeout) { |
|
64 aTab.pluginDoorhangerTimeout = setTimeout(function() { |
|
65 if (this.shouldShowPluginDoorhanger) { |
|
66 PluginHelper.showDoorHanger(this); |
|
67 } |
|
68 }.bind(aTab), 500); |
|
69 } |
|
70 }, |
|
71 |
|
72 playAllPlugins: function(aContentWindow) { |
|
73 let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
74 .getInterface(Ci.nsIDOMWindowUtils); |
|
75 // XXX not sure if we should enable plugins for the parent documents... |
|
76 let plugins = cwu.plugins; |
|
77 if (!plugins || !plugins.length) |
|
78 return; |
|
79 |
|
80 plugins.forEach(this.playPlugin); |
|
81 }, |
|
82 |
|
83 playPlugin: function(plugin) { |
|
84 let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); |
|
85 if (!objLoadingContent.activated) |
|
86 objLoadingContent.playPlugin(); |
|
87 }, |
|
88 |
|
89 stopPlayPreview: function(plugin, playPlugin) { |
|
90 let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); |
|
91 if (objLoadingContent.activated) |
|
92 return; |
|
93 |
|
94 if (playPlugin) |
|
95 objLoadingContent.playPlugin(); |
|
96 else |
|
97 objLoadingContent.cancelPlayPreview(); |
|
98 }, |
|
99 |
|
100 getPluginPreference: function getPluginPreference() { |
|
101 let pluginDisable = Services.prefs.getBoolPref("plugin.disable"); |
|
102 if (pluginDisable) |
|
103 return "0"; |
|
104 |
|
105 let state = Services.prefs.getIntPref("plugin.default.state"); |
|
106 return state == Ci.nsIPluginTag.STATE_CLICKTOPLAY ? "2" : "1"; |
|
107 }, |
|
108 |
|
109 setPluginPreference: function setPluginPreference(aValue) { |
|
110 switch (aValue) { |
|
111 case "0": // Enable Plugins = No |
|
112 Services.prefs.setBoolPref("plugin.disable", true); |
|
113 Services.prefs.clearUserPref("plugin.default.state"); |
|
114 break; |
|
115 case "1": // Enable Plugins = Yes |
|
116 Services.prefs.clearUserPref("plugin.disable"); |
|
117 Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED); |
|
118 break; |
|
119 case "2": // Enable Plugins = Tap to Play (default) |
|
120 Services.prefs.clearUserPref("plugin.disable"); |
|
121 Services.prefs.clearUserPref("plugin.default.state"); |
|
122 break; |
|
123 } |
|
124 }, |
|
125 |
|
126 // Copied from /browser/base/content/browser.js |
|
127 isTooSmall : function (plugin, overlay) { |
|
128 // Is the <object>'s size too small to hold what we want to show? |
|
129 let pluginRect = plugin.getBoundingClientRect(); |
|
130 // XXX bug 446693. The text-shadow on the submitted-report text at |
|
131 // the bottom causes scrollHeight to be larger than it should be. |
|
132 let overflows = (overlay.scrollWidth > pluginRect.width) || |
|
133 (overlay.scrollHeight - 5 > pluginRect.height); |
|
134 |
|
135 return overflows; |
|
136 }, |
|
137 |
|
138 getPluginMimeType: function (plugin) { |
|
139 var tagMimetype = plugin.actualType; |
|
140 |
|
141 if (tagMimetype == "") { |
|
142 tagMimetype = plugin.type; |
|
143 } |
|
144 |
|
145 return tagMimetype; |
|
146 }, |
|
147 |
|
148 handlePluginBindingAttached: function (aTab, aEvent) { |
|
149 let plugin = aEvent.target; |
|
150 let doc = plugin.ownerDocument; |
|
151 let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); |
|
152 if (!overlay || overlay._bindingHandled) { |
|
153 return; |
|
154 } |
|
155 overlay._bindingHandled = true; |
|
156 |
|
157 let eventType = PluginHelper._getBindingType(plugin); |
|
158 if (!eventType) { |
|
159 // Not all bindings have handlers |
|
160 return; |
|
161 } |
|
162 |
|
163 switch (eventType) { |
|
164 case "PluginClickToPlay": { |
|
165 // Check if plugins have already been activated for this page, or if |
|
166 // the user has set a permission to always play plugins on the site |
|
167 if (aTab.clickToPlayPluginsActivated || |
|
168 Services.perms.testPermission(aTab.browser.currentURI, "plugins") == |
|
169 Services.perms.ALLOW_ACTION) { |
|
170 PluginHelper.playPlugin(plugin); |
|
171 return; |
|
172 } |
|
173 |
|
174 // If the plugin is hidden, or if the overlay is too small, show a |
|
175 // doorhanger notification |
|
176 if (PluginHelper.isTooSmall(plugin, overlay)) { |
|
177 PluginHelper.delayAndShowDoorHanger(aTab); |
|
178 } else { |
|
179 // There's a large enough visible overlay that we don't need to show |
|
180 // the doorhanger. |
|
181 aTab.shouldShowPluginDoorhanger = false; |
|
182 overlay.classList.add("visible"); |
|
183 } |
|
184 |
|
185 // Add click to play listener to the overlay |
|
186 overlay.addEventListener("click", function(e) { |
|
187 if (!e.isTrusted) |
|
188 return; |
|
189 e.preventDefault(); |
|
190 let win = e.target.ownerDocument.defaultView.top; |
|
191 let tab = BrowserApp.getTabForWindow(win); |
|
192 tab.clickToPlayPluginsActivated = true; |
|
193 PluginHelper.playAllPlugins(win); |
|
194 |
|
195 NativeWindow.doorhanger.hide("ask-to-play-plugins", tab.id); |
|
196 }, true); |
|
197 |
|
198 // Add handlers for over- and underflow in case the plugin gets resized |
|
199 plugin.addEventListener("overflow", function(event) { |
|
200 overlay.classList.remove("visible"); |
|
201 PluginHelper.delayAndShowDoorHanger(aTab); |
|
202 }); |
|
203 plugin.addEventListener("underflow", function(event) { |
|
204 // This is also triggered if only one dimension underflows, |
|
205 // the other dimension might still overflow |
|
206 if (!PluginHelper.isTooSmall(plugin, overlay)) { |
|
207 overlay.classList.add("visible"); |
|
208 } |
|
209 }); |
|
210 |
|
211 break; |
|
212 } |
|
213 |
|
214 case "PluginPlayPreview": { |
|
215 let previewContent = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent"); |
|
216 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
|
217 let mimeType = PluginHelper.getPluginMimeType(plugin); |
|
218 let playPreviewInfo = pluginHost.getPlayPreviewInfo(mimeType); |
|
219 |
|
220 if (!playPreviewInfo.ignoreCTP) { |
|
221 // Check if plugins have already been activated for this page, or if |
|
222 // the user has set a permission to always play plugins on the site |
|
223 if (aTab.clickToPlayPluginsActivated || |
|
224 Services.perms.testPermission(aTab.browser.currentURI, "plugins") == |
|
225 Services.perms.ALLOW_ACTION) { |
|
226 PluginHelper.playPlugin(plugin); |
|
227 return; |
|
228 } |
|
229 |
|
230 // Always show door hanger for play preview plugins |
|
231 PluginHelper.delayAndShowDoorHanger(aTab); |
|
232 } |
|
233 |
|
234 let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; |
|
235 if (!iframe) { |
|
236 // lazy initialization of the iframe |
|
237 iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); |
|
238 iframe.className = "previewPluginContentFrame"; |
|
239 previewContent.appendChild(iframe); |
|
240 } |
|
241 iframe.src = playPreviewInfo.redirectURL; |
|
242 |
|
243 // MozPlayPlugin event can be dispatched from the extension chrome |
|
244 // code to replace the preview content with the native plugin |
|
245 previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(e) { |
|
246 if (!e.isTrusted) |
|
247 return; |
|
248 |
|
249 previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); |
|
250 |
|
251 let playPlugin = !aEvent.detail; |
|
252 PluginHelper.stopPlayPreview(plugin, playPlugin); |
|
253 |
|
254 // cleaning up: removes overlay iframe from the DOM |
|
255 let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; |
|
256 if (iframe) |
|
257 previewContent.removeChild(iframe); |
|
258 }, true); |
|
259 break; |
|
260 } |
|
261 |
|
262 case "PluginNotFound": { |
|
263 // On devices where we don't support Flash, there will be a |
|
264 // "Learn More..." link in the missing plugin error message. |
|
265 let learnMoreLink = doc.getAnonymousElementByAttribute(plugin, "class", "unsupportedLearnMoreLink"); |
|
266 let learnMoreUrl = Services.urlFormatter.formatURLPref("app.support.baseURL"); |
|
267 learnMoreUrl += "mobile-flash-unsupported"; |
|
268 learnMoreLink.href = learnMoreUrl; |
|
269 overlay.classList.add("visible"); |
|
270 break; |
|
271 } |
|
272 } |
|
273 }, |
|
274 |
|
275 // Helper to get the binding handler type from a plugin object |
|
276 _getBindingType: function(plugin) { |
|
277 if (!(plugin instanceof Ci.nsIObjectLoadingContent)) |
|
278 return null; |
|
279 |
|
280 switch (plugin.pluginFallbackType) { |
|
281 case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: |
|
282 return "PluginNotFound"; |
|
283 case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: |
|
284 return "PluginClickToPlay"; |
|
285 case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW: |
|
286 return "PluginPlayPreview"; |
|
287 default: |
|
288 // Not all states map to a handler |
|
289 return null; |
|
290 } |
|
291 } |
|
292 }; |