Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/objectTabs.js

Issue 29329839: Issue 3228 - Unbreak object tabs (Closed)
Patch Set: Created Nov. 6, 2015, 7:45 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2015 Eyeo GmbH 3 * Copyright (C) 2006-2015 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 /** 18 /**
19 * @fileOverview Code responsible for showing and hiding object tabs. 19 * @fileOverview Code responsible for showing and hiding object tabs.
20 */ 20 */
21 21
22 /** 22 let {Prefs} = require("prefs");
23 * Class responsible for showing and hiding object tabs. 23 let {Utils} = require("utils");
24 * @class 24
25 */ 25 let classVisibleTop = null;
26 var objTabs = 26 let classVisibleBottom = null;
tschuster 2015/11/25 18:50:13 Please add a description to those.
Wladimir Palant 2015/11/25 22:50:32 Done.
27 let classHidden = null;
28
29 Utils.addChildMessageListener("AdblockPlus:GetObjectTabsStatus", function()
27 { 30 {
28 /** 31 let {UI} = require("ui");
29 * Number of milliseconds to wait until hiding tab after the mouse moves away.
30 * @type Integer
31 */
32 HIDE_DELAY: 1000,
33 32
34 /** 33 return !!(Prefs.enabled && Prefs.frameobjects && UI.overlay && classHidden);
35 * Flag used to trigger object tabs initialization first time object tabs are 34 });
36 * used.
37 * @type Boolean
38 */
39 initialized: false,
40 35
41 /** 36 Utils.addChildMessageListener("AdblockPlus:GetObjectTabsTexts", function()
42 * Will be set to true while initialization is in progress. 37 {
43 * @type Boolean 38 let {UI} = require("ui");
44 */
45 initializing: false,
46 39
47 /** 40 return {
48 * Parameters for _showTab, to be called once initialization is complete. 41 label: UI.overlay.attributes.objtabtext,
49 */ 42 tooltip: UI.overlay.attributes.objtabtooltip,
50 delayedShowParams: null, 43 classVisibleTop, classVisibleBottom, classHidden
44 };
45 });
51 46
52 /** 47 Utils.addChildMessageListener("AdblockPlus:BlockItem", function(item)
53 * Randomly generated class to be used for visible object tabs on top of objec t. 48 {
54 * @type String 49 let {UI} = require("ui");
55 */ 50 UI.blockItem(UI.currentWindow, null, item);
56 objTabClassVisibleTop: null, 51 });
57 52
58 /** 53 function init()
Wladimir Palant 2015/11/06 19:52:43 This is based on the _initCSS() function, but with
tschuster 2015/11/25 18:50:13 Thanks, that was helpful.
59 * Randomly generated class to be used for visible object tabs at the bottom o f the object. 54 {
60 * @type String 55 function processCSSData(event)
61 */
62 objTabClassVisibleBottom: null,
63
64 /**
65 * Randomly generated class to be used for invisible object tabs.
66 * @type String
67 */
68 objTabClassHidden: null,
69
70 /**
71 * Document element the object tab is currently being displayed for.
72 * @type Element
73 */
74 currentElement: null,
75
76 /**
77 * Windows that the window event handler is currently registered for.
78 * @type Window[]
79 */
80 windowListeners: null,
81
82 /**
83 * Panel element currently used as object tab.
84 * @type Element
85 */
86 objtabElement: null,
87
88 /**
89 * Time of previous position update.
90 * @type Integer
91 */
92 prevPositionUpdate: 0,
93
94 /**
95 * Timer used to update position of the object tab.
96 * @type nsITimer
97 */
98 positionTimer: null,
99
100 /**
101 * Timer used to delay hiding of the object tab.
102 * @type nsITimer
103 */
104 hideTimer: null,
105
106 /**
107 * Used when hideTimer is running, time when the tab should be hidden.
108 * @type Integer
109 */
110 hideTargetTime: 0,
111
112 /**
113 * Initializes object tabs (generates random classes and registers stylesheet) .
114 */
115 _initCSS: function()
116 { 56 {
117 function processCSSData(request) 57 if (onShutdown.done)
118 {
119 if (onShutdown.done)
120 return;
121
122 let data = request.responseText;
123
124 let rnd = [];
125 let offset = "a".charCodeAt(0);
126 for (let i = 0; i < 60; i++)
127 rnd.push(offset + Math.random() * 26);
128
129 this.objTabClassVisibleTop = String.fromCharCode.apply(String, rnd.slice(0 , 20));
130 this.objTabClassVisibleBottom = String.fromCharCode.apply(String, rnd.slic e(20, 40));
131 this.objTabClassHidden = String.fromCharCode.apply(String, rnd.slice(40, 6 0));
132
133 let {Utils} = require("utils");
134 let url = Utils.makeURI("data:text/css," + encodeURIComponent(data.replace (/%%CLASSVISIBLETOP%%/g, this.objTabClassVisibleTop)
135 .replace (/%%CLASSVISIBLEBOTTOM%%/g, this.objTabClassVisibleBottom)
136 .replace (/%%CLASSHIDDEN%%/g, this.objTabClassHidden)));
137 Utils.styleService.loadAndRegisterSheet(url, Ci.nsIStyleSheetService.USER_ SHEET);
138 onShutdown.add(function()
139 {
140 Utils.styleService.unregisterSheet(url, Ci.nsIStyleSheetService.USER_SHE ET);
141 });
142
143 this.initializing = false;
144 this.initialized = true;
145
146 if (this.delayedShowParams)
147 this._showTab.apply(this, this.delayedShowParams);
148 }
149
150 this.delayedShowParams = arguments;
151
152 if (!this.initializing)
153 {
154 this.initializing = true;
155
156 // Load CSS asynchronously
157 try {
158 let request = new XMLHttpRequest();
159 request.mozBackgroundRequest = true;
160 request.open("GET", "chrome://adblockplus/content/objtabs.css");
161 request.overrideMimeType("text/plain");
162
163 request.addEventListener("load", processCSSData.bind(this, request), fal se);
164 request.send(null);
165 }
166 catch (e)
167 {
168 Cu.reportError(e);
169 this.initializing = false;
170 }
171 }
172 },
173
174 /**
175 * Called to show object tab for an element.
176 */
177 showTabFor: function(/**Element*/ element)
178 {
179 // Object tabs aren't usable in Fennec
180 let {application} = require("info");
181 if (application == "fennec" || application == "fennec2" ||
182 application == "adblockbrowser")
183 return; 58 return;
184 59
185 let {Prefs} = require("prefs"); 60 let data = event.target.responseText;
186 if (!Prefs.frameobjects)
187 return;
188 61
189 if (this.hideTimer) 62 let rnd = [];
63 let offset = "a".charCodeAt(0);
64 for (let i = 0; i < 60; i++)
65 rnd.push(offset + Math.random() * 26);
66
67 classVisibleTop = String.fromCharCode.apply(String, rnd.slice(0, 20));
68 classVisibleBottom = String.fromCharCode.apply(String, rnd.slice(20, 40));
69 classHidden = String.fromCharCode.apply(String, rnd.slice(40, 60));
70
71 let url = Utils.makeURI("data:text/css," + encodeURIComponent(data.replace(/ %%CLASSVISIBLETOP%%/g, classVisibleTop)
72 .replace(/ %%CLASSVISIBLEBOTTOM%%/g, classVisibleBottom)
73 .replace(/ %%CLASSHIDDEN%%/g, classHidden)));
74 Utils.styleService.loadAndRegisterSheet(url, Ci.nsIStyleSheetService.USER_SH EET);
75 onShutdown.add(function()
190 { 76 {
191 this.hideTimer.cancel(); 77 Utils.styleService.unregisterSheet(url, Ci.nsIStyleSheetService.USER_SHEET );
192 this.hideTimer = null; 78 });
193 } 79 }
194 80
195 if (this.objtabElement) 81 // Load CSS asynchronously
196 this.objtabElement.style.setProperty("opacity", "1", "important"); 82 try
197
198 if (this.currentElement != element)
199 {
200 this._hideTab();
201
202 let {Policy} = require("contentPolicy");
203 let {RequestNotifier} = require("requestNotifier");
204 let data = RequestNotifier.getDataForNode(element, true, "OBJECT");
205 if (data)
206 {
207 if (this.initialized)
208 this._showTab(element, data[1]);
209 else
210 this._initCSS(element, data[1]);
211 }
212 }
213 },
214
215 /**
216 * Called to hide object tab for an element (actual hiding happens delayed).
217 */
218 hideTabFor: function(/**Element*/ element)
219 { 83 {
220 if (element != this.currentElement || this.hideTimer) 84 let request = new XMLHttpRequest();
221 return; 85 request.mozBackgroundRequest = true;
222 86 request.open("GET", "chrome://adblockplus/content/objtabs.css");
223 this.hideTargetTime = Date.now() + this.HIDE_DELAY; 87 request.overrideMimeType("text/plain");
224 this.hideTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 88 request.addEventListener("load", processCSSData, false);
225 this.hideTimer.init(this, 40, Ci.nsITimer.TYPE_REPEATING_SLACK); 89 request.send(null);
226 }, 90 }
227 91 catch (e)
228 /**
229 * Makes the tab element visible.
230 * @param {Element} element
231 * @param {RequestEntry} data
232 */
233 _showTab: function(element, data)
234 { 92 {
235 let {UI} = require("ui"); 93 Cu.reportError(e);
236 if (!UI.overlay)
237 return;
238
239 let doc = element.ownerDocument.defaultView.top.document;
240
241 this.objtabElement = doc.createElementNS("http://www.w3.org/1999/xhtml", "a" );
242 this.objtabElement.textContent = UI.overlay.attributes.objtabtext;
243 this.objtabElement.setAttribute("title", UI.overlay.attributes.objtabtooltip );
244 this.objtabElement.setAttribute("href", data.location);
245 this.objtabElement.setAttribute("class", this.objTabClassHidden);
246 this.objtabElement.style.setProperty("opacity", "1", "important");
247 this.objtabElement.nodeData = data;
248
249 this.currentElement = element;
250
251 // Register paint listeners for the relevant windows
252 this.windowListeners = [];
253 let wnd = element.ownerDocument.defaultView;
254 while (wnd)
255 {
256 wnd.addEventListener("MozAfterPaint", objectWindowEventHandler, false);
257 this.windowListeners.push(wnd);
258 wnd = (wnd.parent != wnd ? wnd.parent : null);
259 }
260
261 // Register mouse listeners on the object tab
262 this.objtabElement.addEventListener("mouseover", objectTabEventHander, false );
263 this.objtabElement.addEventListener("mouseout", objectTabEventHander, false) ;
264 this.objtabElement.addEventListener("click", objectTabEventHander, true);
265
266 // Insert the tab into the document and adjust its position
267 doc.documentElement.appendChild(this.objtabElement);
268 if (!this.positionTimer)
269 {
270 this.positionTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer );
271 this.positionTimer.init(this, 200, Ci.nsITimer.TYPE_REPEATING_SLACK);
272 }
273 this._positionTab();
274 },
275
276 /**
277 * Hides the tab element.
278 */
279 _hideTab: function()
280 {
281 this.delayedShowParams = null;
282
283 if (this.objtabElement)
284 {
285 // Prevent recursive calls via popuphidden handler
286 let objtab = this.objtabElement;
287 this.objtabElement = null;
288 this.currentElement = null;
289
290 if (this.hideTimer)
291 {
292 this.hideTimer.cancel();
293 this.hideTimer = null;
294 }
295
296 if (this.positionTimer)
297 {
298 this.positionTimer.cancel();
299 this.positionTimer = null;
300 }
301
302 try {
303 objtab.parentNode.removeChild(objtab);
304 } catch (e) {}
305 objtab.removeEventListener("mouseover", objectTabEventHander, false);
306 objtab.removeEventListener("mouseout", objectTabEventHander, false);
307 objtab.nodeData = null;
308
309 for (let wnd of this.windowListeners)
310 wnd.removeEventListener("MozAfterPaint", objectWindowEventHandler, false );
311 this.windowListeners = null;
312 }
313 },
314
315 /**
316 * Updates position of the tab element.
317 */
318 _positionTab: function()
319 {
320 // Test whether element is still in document
321 let elementDoc = null;
322 try
323 {
324 elementDoc = this.currentElement.ownerDocument;
325 } catch (e) {} // Ignore "can't access dead object" error
326 if (!elementDoc || !this.currentElement.offsetWidth || !this.currentElement. offsetHeight ||
327 !elementDoc.defaultView || !elementDoc.documentElement)
328 {
329 this._hideTab();
330 return;
331 }
332
333 let objRect = this._getElementPosition(this.currentElement);
334
335 let className = this.objTabClassVisibleTop;
336 let left = objRect.right - this.objtabElement.offsetWidth;
337 let top = objRect.top - this.objtabElement.offsetHeight;
338 if (top < 0)
339 {
340 top = objRect.bottom;
341 className = this.objTabClassVisibleBottom;
342 }
343
344 if (this.objtabElement.style.left != left + "px")
345 this.objtabElement.style.setProperty("left", left + "px", "important");
346 if (this.objtabElement.style.top != top + "px")
347 this.objtabElement.style.setProperty("top", top + "px", "important");
348
349 if (this.objtabElement.getAttribute("class") != className)
350 this.objtabElement.setAttribute("class", className);
351
352 this.prevPositionUpdate = Date.now();
353 },
354
355 /**
356 * Calculates element's position relative to the top frame and considering
357 * clipping due to scrolling.
358 * @return {{left: Number, top: Number, right: Number, bottom: Number}}
359 */
360 _getElementPosition: function(/**Element*/ element)
361 {
362 // Restrict rectangle coordinates by the boundaries of a window's client are a
363 function intersectRect(rect, wnd)
364 {
365 // Cannot use wnd.innerWidth/Height because they won't account for scrollb ars
366 let doc = wnd.document;
367 let wndWidth = doc.documentElement.clientWidth;
368 let wndHeight = doc.documentElement.clientHeight;
369 if (doc.compatMode == "BackCompat") // clientHeight will be bogus in quirk s mode
370 wndHeight = Math.max(doc.documentElement.offsetHeight, doc.body.offsetHe ight) - wnd.scrollMaxY - 1;
371
372 rect.left = Math.max(rect.left, 0);
373 rect.top = Math.max(rect.top, 0);
374 rect.right = Math.min(rect.right, wndWidth);
375 rect.bottom = Math.min(rect.bottom, wndHeight);
376 }
377
378 let rect = element.getBoundingClientRect();
379 let wnd = element.ownerDocument.defaultView;
380
381 let style = wnd.getComputedStyle(element, null);
382 let offsets = [
383 parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft),
384 parseFloat(style.borderTopWidth) + parseFloat(style.paddingTop),
385 parseFloat(style.borderRightWidth) + parseFloat(style.paddingRight),
386 parseFloat(style.borderBottomWidth) + parseFloat(style.paddingBottom)
387 ];
388
389 rect = {left: rect.left + offsets[0], top: rect.top + offsets[1],
390 right: rect.right - offsets[2], bottom: rect.bottom - offsets[3]};
391 while (true)
392 {
393 intersectRect(rect, wnd);
394
395 if (!wnd.frameElement)
396 break;
397
398 // Recalculate coordinates to be relative to frame's parent window
399 let frameElement = wnd.frameElement;
400 wnd = frameElement.ownerDocument.defaultView;
401
402 let frameRect = frameElement.getBoundingClientRect();
403 let frameStyle = wnd.getComputedStyle(frameElement, null);
404 let relLeft = frameRect.left + parseFloat(frameStyle.borderLeftWidth) + pa rseFloat(frameStyle.paddingLeft);
405 let relTop = frameRect.top + parseFloat(frameStyle.borderTopWidth) + parse Float(frameStyle.paddingTop);
406
407 rect.left += relLeft;
408 rect.right += relLeft;
409 rect.top += relTop;
410 rect.bottom += relTop;
411 }
412
413 return rect;
414 },
415
416 doBlock: function()
417 {
418 let {UI} = require("ui");
419 let {Utils} = require("utils");
420 let chromeWindow = Utils.getChromeWindow(this.currentElement.ownerDocument.d efaultView);
421 UI.blockItem(chromeWindow, this.currentElement, this.objtabElement.nodeData) ;
422 },
423
424 /**
425 * Called whenever a timer fires.
426 * @param {nsISupport} subject
427 * @param {string} topic
428 * @param {string} data
429 */
430 observe: function(subject, topic, data)
431 {
432 if (subject == this.positionTimer)
433 {
434 // Don't update position if it was already updated recently (via MozAfterP aint)
435 if (Date.now() - this.prevPositionUpdate > 100)
436 this._positionTab();
437 }
438 else if (subject == this.hideTimer)
439 {
440 let now = Date.now();
441 if (now >= this.hideTargetTime)
442 this._hideTab();
443 else if (this.hideTargetTime - now < this.HIDE_DELAY / 2)
444 this.objtabElement.style.setProperty("opacity", (this.hideTargetTime - n ow) * 2 / this.HIDE_DELAY, "important");
445 }
446 } 94 }
447 };
448
449 onShutdown.add(objTabs._hideTab.bind(objTabs));
450
451 /**
452 * Function called whenever the mouse enters or leaves an object.
453 */
454 function objectMouseEventHander(/**Event*/ event)
455 {
456 if (!event.isTrusted)
457 return;
458
459 if (event.type == "mouseover")
460 objTabs.showTabFor(event.target);
461 else if (event.type == "mouseout")
462 objTabs.hideTabFor(event.target);
463 } 95 }
464 96 init();
465 /**
466 * Function called for paint events of the object tab window.
467 */
468 function objectWindowEventHandler(/**Event*/ event)
469 {
470 if (!event.isTrusted)
471 return;
472
473 // Don't trigger update too often, avoid overusing CPU on frequent page update s
474 if (event.type == "MozAfterPaint" && Date.now() - objTabs.prevPositionUpdate > 20)
475 objTabs._positionTab();
476 }
477
478 /**
479 * Function called whenever the mouse enters or leaves an object tab.
480 */
481 function objectTabEventHander(/**Event*/ event)
482 {
483 if (onShutdown.done || !event.isTrusted)
484 return;
485
486 if (event.type == "click" && event.button == 0)
487 {
488 event.preventDefault();
489 event.stopPropagation();
490
491 objTabs.doBlock();
492 }
493 else if (event.type == "mouseover")
494 objTabs.showTabFor(objTabs.currentElement);
495 else if (event.type == "mouseout")
496 objTabs.hideTabFor(objTabs.currentElement);
497 }
498 exports.objectMouseEventHander = objectMouseEventHander;
OLDNEW
« lib/contentPolicy.js ('K') | « lib/main.js ('k') | lib/requestNotifier.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld