vapi-background.js 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
  4. Copyright (C) 2019-2022 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. /* jshint bitwise: false, esnext: true */
  19. /* global self, Components */
  20. // For background page
  21. 'use strict';
  22. /******************************************************************************/
  23. (function () {
  24. Cu.import('chrome://ematrix/content/lib/HttpRequestHeaders.jsm');
  25. Cu.import('chrome://ematrix/content/lib/PendingRequests.jsm');
  26. Cu.import('chrome://ematrix/content/lib/Punycode.jsm');
  27. Cu.import('chrome://ematrix/content/lib/HostMap.jsm');
  28. // Icon-related stuff
  29. vAPI.setIcon = function (tabId, iconId, badge) {
  30. // If badge is undefined, then setIcon was called from the
  31. // TabSelect event
  32. let win;
  33. if (badge === undefined) {
  34. win = iconId;
  35. } else {
  36. win = vAPI.window.getCurrentWindow();
  37. }
  38. let tabBrowser = vAPI.browser.getTabBrowser(win);
  39. if (tabBrowser === null) {
  40. return;
  41. }
  42. let curTabId = vAPI.tabs.manager.tabIdFromTarget(tabBrowser.selectedTab);
  43. let tb = vAPI.toolbarButton;
  44. // from 'TabSelect' event
  45. if (tabId === undefined) {
  46. tabId = curTabId;
  47. } else if (badge !== undefined) {
  48. tb.tabs[tabId] = {
  49. badge: badge, img: iconId
  50. };
  51. }
  52. if (tabId === curTabId) {
  53. tb.updateState(win, tabId);
  54. }
  55. };
  56. vAPI.httpObserver = {
  57. classDescription: 'net-channel-event-sinks for ' + location.host,
  58. classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'),
  59. contractID: '@' + location.host + '/net-channel-event-sinks;1',
  60. REQDATAKEY: location.host + 'reqdata',
  61. ABORT: Components.results.NS_BINDING_ABORTED,
  62. ACCEPT: Components.results.NS_SUCCEEDED,
  63. // Request types:
  64. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants
  65. // eMatrix: we can just use nsIContentPolicy's built-in
  66. // constants, can we?
  67. frameTypeMap: {
  68. 6: 'main_frame',
  69. 7: 'sub_frame'
  70. },
  71. typeMap: {
  72. 1: 'other',
  73. 2: 'script',
  74. 3: 'image',
  75. 4: 'stylesheet',
  76. 5: 'object',
  77. 6: 'main_frame',
  78. 7: 'sub_frame',
  79. 9: 'xbl',
  80. 10: 'ping',
  81. 11: 'xmlhttprequest',
  82. 12: 'object',
  83. 13: 'xml_dtd',
  84. 14: 'font',
  85. 15: 'media',
  86. 16: 'websocket',
  87. 17: 'csp_report',
  88. 18: 'xslt',
  89. 19: 'beacon',
  90. 20: 'xmlhttprequest',
  91. 21: 'imageset',
  92. 22: 'web_manifest'
  93. },
  94. mimeTypeMap: {
  95. 'audio': 15,
  96. 'video': 15
  97. },
  98. get componentRegistrar () {
  99. return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
  100. },
  101. get categoryManager () {
  102. return Cc['@mozilla.org/categorymanager;1']
  103. .getService(Ci.nsICategoryManager);
  104. },
  105. QueryInterface: (function () {
  106. var {XPCOMUtils} =
  107. Cu.import('resource://gre/modules/XPCOMUtils.jsm', null);
  108. return XPCOMUtils.generateQI([
  109. Ci.nsIFactory,
  110. Ci.nsIObserver,
  111. Ci.nsIChannelEventSink,
  112. Ci.nsISupportsWeakReference
  113. ]);
  114. })(),
  115. createInstance: function (outer, iid) {
  116. if (outer) {
  117. throw Components.results.NS_ERROR_NO_AGGREGATION;
  118. }
  119. return this.QueryInterface(iid);
  120. },
  121. register: function () {
  122. Services.obs.addObserver(this, 'http-on-modify-request', true);
  123. Services.obs.addObserver(this, 'http-on-examine-response', true);
  124. Services.obs.addObserver(this, 'http-on-examine-cached-response', true);
  125. // Guard against stale instances not having been unregistered
  126. if (this.componentRegistrar.isCIDRegistered(this.classID)) {
  127. try {
  128. this.componentRegistrar
  129. .unregisterFactory(this.classID,
  130. Components.manager
  131. .getClassObject(this.classID,
  132. Ci.nsIFactory));
  133. } catch (ex) {
  134. console.error('eMatrix> httpObserver > '
  135. +'unable to unregister stale instance: ', ex);
  136. }
  137. }
  138. this.componentRegistrar.registerFactory(this.classID,
  139. this.classDescription,
  140. this.contractID,
  141. this);
  142. this.categoryManager.addCategoryEntry('net-channel-event-sinks',
  143. this.contractID,
  144. this.contractID,
  145. false,
  146. true);
  147. },
  148. unregister: function () {
  149. Services.obs.removeObserver(this, 'http-on-modify-request');
  150. Services.obs.removeObserver(this, 'http-on-examine-response');
  151. Services.obs.removeObserver(this, 'http-on-examine-cached-response');
  152. this.componentRegistrar.unregisterFactory(this.classID, this);
  153. this.categoryManager.deleteCategoryEntry('net-channel-event-sinks',
  154. this.contractID,
  155. false);
  156. },
  157. handleRequest: function (channel, URI, tabId, rawType) {
  158. let type = this.typeMap[rawType] || 'other';
  159. let onBeforeRequest = vAPI.net.onBeforeRequest;
  160. if (onBeforeRequest.types === null
  161. || onBeforeRequest.types.has(type)) {
  162. let result = onBeforeRequest.callback({
  163. parentFrameId: type === 'main_frame' ? -1 : 0,
  164. tabId: tabId,
  165. type: type,
  166. url: URI.asciiSpec
  167. });
  168. if (typeof result === 'object') {
  169. channel.cancel(this.ABORT);
  170. return true;
  171. }
  172. }
  173. let onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders;
  174. if (onBeforeSendHeaders.types === null
  175. || onBeforeSendHeaders.types.has(type)) {
  176. let requestHeaders = HTTPRequestHeaders.factory(channel);
  177. let newHeaders = onBeforeSendHeaders.callback({
  178. parentFrameId: type === 'main_frame' ? -1 : 0,
  179. requestHeaders: requestHeaders.headers,
  180. tabId: tabId,
  181. type: type,
  182. url: URI.asciiSpec,
  183. method: channel.requestMethod
  184. });
  185. if (newHeaders) {
  186. requestHeaders.update();
  187. }
  188. requestHeaders.dispose();
  189. }
  190. return false;
  191. },
  192. channelDataFromChannel: function (channel) {
  193. if (channel instanceof Ci.nsIWritablePropertyBag) {
  194. try {
  195. return channel.getProperty(this.REQDATAKEY) || null;
  196. } catch (ex) {
  197. // Ignore
  198. }
  199. }
  200. return null;
  201. },
  202. // https://github.com/gorhill/uMatrix/issues/165
  203. // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request
  204. // Not sure `ematrix:shouldLoad` is still needed, eMatrix does
  205. // not care about embedded frames topography.
  206. // Also:
  207. // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts
  208. tabIdFromChannel: function (channel) {
  209. let lc;
  210. try {
  211. lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
  212. } catch(ex) {
  213. // Ignore
  214. }
  215. if (!lc) {
  216. try {
  217. lc = channel.loadGroup.notificationCallbacks
  218. .getInterface(Ci.nsILoadContext);
  219. } catch(ex) {
  220. // Ignore
  221. }
  222. if (!lc) {
  223. return vAPI.noTabId;
  224. }
  225. }
  226. if (lc.topFrameElement) {
  227. return vAPI.tabs.manager.tabIdFromTarget(lc.topFrameElement);
  228. }
  229. let win;
  230. try {
  231. win = lc.associatedWindow;
  232. } catch (ex) {
  233. // Ignore
  234. }
  235. if (!win) {
  236. return vAPI.noTabId;
  237. }
  238. if (win.top) {
  239. win = win.top;
  240. }
  241. let tabBrowser;
  242. try {
  243. tabBrowser =
  244. vAPI.browser.getTabBrowser
  245. (win.QueryInterface(Ci.nsIInterfaceRequestor)
  246. .getInterface(Ci.nsIWebNavigation)
  247. .QueryInterface(Ci.nsIDocShell).rootTreeItem
  248. .QueryInterface(Ci.nsIInterfaceRequestor)
  249. .getInterface(Ci.nsIDOMWindow));
  250. } catch (ex) {
  251. // Ignore
  252. }
  253. if (!tabBrowser) {
  254. return vAPI.noTabId;
  255. }
  256. if (tabBrowser.getBrowserForContentWindow) {
  257. return vAPI.tabs.manager
  258. .tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win));
  259. }
  260. // Falling back onto _getTabForContentWindow to ensure older
  261. // versions of Firefox work well.
  262. return tabBrowser._getTabForContentWindow
  263. ? vAPI.tabs.manager
  264. .tabIdFromTarget(tabBrowser._getTabForContentWindow(win))
  265. : vAPI.noTabId;
  266. },
  267. rawtypeFromContentType: function (channel) {
  268. let mime = channel.contentType;
  269. if (!mime) {
  270. return 0;
  271. }
  272. let pos = mime.indexOf('/');
  273. if (pos === -1) {
  274. pos = mime.length;
  275. }
  276. return this.mimeTypeMap[mime.slice(0, pos)] || 0;
  277. },
  278. operate: function (channel, URI, topic) {
  279. let channelData = this.channelDataFromChannel(channel);
  280. if (topic.lastIndexOf('http-on-examine-', 0) === 0) {
  281. if (channelData === null) {
  282. return;
  283. }
  284. let type = this.frameTypeMap[channelData[1]];
  285. if (!type) {
  286. return;
  287. }
  288. // topic = ['Content-Security-Policy',
  289. // 'Content-Security-Policy-Report-Only'];
  290. //
  291. // Can send empty responseHeaders as these headers are
  292. // only added to and then merged.
  293. //
  294. // TODO: Find better place for this, needs to be set
  295. // before onHeadersReceived.callback. Web workers not
  296. // blocked in Pale Moon as child-src currently
  297. // unavailable, see:
  298. //
  299. // https://github.com/MoonchildProductions/Pale-Moon/issues/949
  300. //
  301. // eMatrix: as of Pale Moon 28 it seems child-src is
  302. // available and depracated(?)
  303. if (ηMatrix.cspNoWorker === undefined) {
  304. // ηMatrix.cspNoWorker = "child-src 'none'; "
  305. // +"frame-src data: blob: *; "
  306. // +"report-uri about:blank";
  307. ηMatrix.cspNoWorker = "worker-src 'none'; "
  308. +"frame-src data: blob: *; "
  309. +"report-uri about:blank";
  310. }
  311. let result = vAPI.net.onHeadersReceived.callback({
  312. parentFrameId: type === 'main_frame' ? -1 : 0,
  313. responseHeaders: [],
  314. tabId: channelData[0],
  315. type: type,
  316. url: URI.asciiSpec
  317. });
  318. if (result) {
  319. for (let header of result.responseHeaders) {
  320. channel.setResponseHeader(header.name,
  321. header.value,
  322. true);
  323. }
  324. }
  325. return;
  326. }
  327. // http-on-modify-request
  328. // The channel was previously serviced.
  329. if (channelData !== null) {
  330. this.handleRequest(channel, URI,
  331. channelData[0], channelData[1]);
  332. return;
  333. }
  334. // The channel was never serviced.
  335. let tabId;
  336. let pendingRequest =
  337. PendingRequestBuffer.lookupRequest(URI.asciiSpec);
  338. let rawType = 1;
  339. let loadInfo = channel.loadInfo;
  340. // https://github.com/gorhill/uMatrix/issues/390#issuecomment-155717004
  341. if (loadInfo) {
  342. rawType = (loadInfo.externalContentPolicyType !== undefined)
  343. ? loadInfo.externalContentPolicyType
  344. : loadInfo.contentPolicyType;
  345. if (!rawType) {
  346. rawType = 1;
  347. }
  348. }
  349. if (pendingRequest !== null) {
  350. tabId = pendingRequest.tabId;
  351. // https://github.com/gorhill/uBlock/issues/654
  352. // Use the request type from the HTTP observer point of view.
  353. if (rawType !== 1) {
  354. pendingRequest.rawType = rawType;
  355. } else {
  356. rawType = pendingRequest.rawType;
  357. }
  358. } else {
  359. tabId = this.tabIdFromChannel(channel);
  360. }
  361. if (this.handleRequest(channel, URI, tabId, rawType)) {
  362. return;
  363. }
  364. if (channel instanceof Ci.nsIWritablePropertyBag === false) {
  365. return;
  366. }
  367. // Carry data for behind-the-scene redirects
  368. channel.setProperty(this.REQDATAKEY, [tabId, rawType]);
  369. },
  370. observe: function (channel, topic) {
  371. if (channel instanceof Ci.nsIHttpChannel === false) {
  372. return;
  373. }
  374. let URI = channel.URI;
  375. let channelData = this.channelDataFromChannel(channel);
  376. if (ηMatrix.userSettings.resolveCname === true) {
  377. let key = URI.scheme + '://' + URI.host;
  378. if (HostMap.get(key)) {
  379. this.operate(channel, HostMap.get(key), topic);
  380. return;
  381. }
  382. let CC = Components.classes;
  383. let CI = Components.interfaces;
  384. let tab = this.tabIdFromChannel(channel);
  385. let dns = CC['@mozilla.org/network/dns-service;1']
  386. .createInstance(CI.nsIDNSService);
  387. let listener = {
  388. onLookupComplete: function (req, rec, stat) {
  389. if (!Components.isSuccessCode(stat)) {
  390. console.error("can't resolve canonical name");
  391. return;
  392. }
  393. let addr = rec.canonicalName;
  394. ηMatrix.logger.writeOne(tab, 'info',
  395. URI.host + ' => ' + addr);
  396. let ios = CC['@mozilla.org/network/io-service;1']
  397. .createInstance(CI.nsIIOService);
  398. let uri = ios.newURI(URI.scheme+'://'+addr, null, null);
  399. HostMap.put(key, uri);
  400. vAPI.httpObserver.operate(channel, uri, topic);
  401. },
  402. };
  403. dns.asyncResolve(URI.host,
  404. CI.nsIDNSService.RESOLVE_CANONICAL_NAME,
  405. listener,
  406. null);
  407. } else {
  408. this.operate(channel, URI, topic);
  409. }
  410. },
  411. asyncOnChannelRedirect: function (oldChannel, newChannel,
  412. flags, callback) {
  413. // contentPolicy.shouldLoad doesn't detect redirects, this
  414. // needs to be used
  415. // If error thrown, the redirect will fail
  416. try {
  417. let URI = newChannel.URI;
  418. if (!URI.schemeIs('http') && !URI.schemeIs('https')) {
  419. return;
  420. }
  421. if (newChannel instanceof Ci.nsIWritablePropertyBag === false) {
  422. return;
  423. }
  424. let channelData = this.channelDataFromChannel(oldChannel);
  425. if (channelData === null) {
  426. return;
  427. }
  428. // Carry the data on in case of multiple redirects
  429. newChannel.setProperty(this.REQDATAKEY, channelData);
  430. } catch (ex) {
  431. // console.error(ex);
  432. // Ignore
  433. } finally {
  434. callback.onRedirectVerifyCallback(this.ACCEPT);
  435. }
  436. }
  437. };
  438. vAPI.toolbarButton = {
  439. id: location.host + '-button',
  440. type: 'view',
  441. viewId: location.host + '-panel',
  442. label: vAPI.app.name,
  443. tooltiptext: vAPI.app.name,
  444. tabs: {/*tabId: {badge: 0, img: boolean}*/},
  445. init: null,
  446. codePath: ''
  447. };
  448. (function () {
  449. let tbb = vAPI.toolbarButton;
  450. let popupCommittedWidth = 0;
  451. let popupCommittedHeight = 0;
  452. tbb.onViewShowing = function ({target}) {
  453. popupCommittedWidth = popupCommittedHeight = 0;
  454. target.firstChild.setAttribute('src', vAPI.getURL('popup.html'));
  455. };
  456. tbb.onViewHiding = function ({target}) {
  457. target.parentNode.style.maxWidth = '';
  458. target.firstChild.setAttribute('src', 'about:blank');
  459. };
  460. tbb.updateState = function (win, tabId) {
  461. let button = win.document.getElementById(this.id);
  462. if (!button) {
  463. return;
  464. }
  465. let icon = this.tabs[tabId];
  466. button.setAttribute('badge', icon && icon.badge || '');
  467. button.classList.toggle('off', !icon || !icon.img);
  468. let iconId = (ηMatrix.userSettings.disableUpdateIcon) ?
  469. icon && icon.img ? '19' : 'off' :
  470. icon && icon.img ? icon.img : 'off';
  471. icon = 'url('
  472. + vAPI.getURL('img/browsericons/icon19-'
  473. + iconId
  474. + '.png')
  475. + ')';
  476. button.style.listStyleImage = icon;
  477. };
  478. tbb.populatePanel = function (doc, panel) {
  479. panel.setAttribute('id', this.viewId);
  480. let iframe = doc.createElement('iframe');
  481. iframe.setAttribute('type', 'content');
  482. panel.appendChild(iframe);
  483. let toPx = function (pixels) {
  484. return pixels.toString() + 'px';
  485. };
  486. let scrollBarWidth = 0;
  487. let resizeTimer = null;
  488. let resizePopupDelayed = function (attempts) {
  489. if (resizeTimer !== null) {
  490. return false;
  491. }
  492. // Sanity check
  493. attempts = (attempts || 0) + 1;
  494. if ( attempts > 1/*000*/ ) {
  495. //console.error('eMatrix> resizePopupDelayed: giving up after too many attempts');
  496. return false;
  497. }
  498. resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts);
  499. return true;
  500. };
  501. let resizePopup = function (attempts) {
  502. resizeTimer = null;
  503. panel.parentNode.style.maxWidth = 'none';
  504. let body = iframe.contentDocument.body;
  505. // https://github.com/gorhill/uMatrix/issues/301
  506. // Don't resize if committed size did not change.
  507. if (popupCommittedWidth === body.clientWidth
  508. && popupCommittedHeight === body.clientHeight) {
  509. return;
  510. }
  511. // We set a limit for height
  512. let height = Math.min(body.clientHeight, 600);
  513. // https://github.com/chrisaljoudi/uBlock/issues/730
  514. // Voodoo programming: this recipe works
  515. panel.style.setProperty('height', toPx(height));
  516. iframe.style.setProperty('height', toPx(height));
  517. // Adjust width for presence/absence of vertical scroll bar which may
  518. // have appeared as a result of last operation.
  519. let contentWindow = iframe.contentWindow;
  520. let width = body.clientWidth;
  521. if (contentWindow.scrollMaxY !== 0) {
  522. width += scrollBarWidth;
  523. }
  524. panel.style.setProperty('width', toPx(width));
  525. // scrollMaxX should always be zero once we know the scrollbar width
  526. if (contentWindow.scrollMaxX !== 0) {
  527. scrollBarWidth = contentWindow.scrollMaxX;
  528. width += scrollBarWidth;
  529. panel.style.setProperty('width', toPx(width));
  530. }
  531. if (iframe.clientHeight !== height
  532. || panel.clientWidth !== width) {
  533. if (resizePopupDelayed(attempts)) {
  534. return;
  535. }
  536. // resizePopupDelayed won't be called again, so commit
  537. // dimentsions.
  538. }
  539. popupCommittedWidth = body.clientWidth;
  540. popupCommittedHeight = body.clientHeight;
  541. };
  542. let onResizeRequested = function () {
  543. let body = iframe.contentDocument.body;
  544. if (body.getAttribute('data-resize-popup') !== 'true') {
  545. return;
  546. }
  547. body.removeAttribute('data-resize-popup');
  548. resizePopupDelayed();
  549. };
  550. let onPopupReady = function () {
  551. let win = this.contentWindow;
  552. if (!win || win.location.host !== location.host) {
  553. return;
  554. }
  555. if (typeof tbb.onBeforePopupReady === 'function') {
  556. tbb.onBeforePopupReady.call(this);
  557. }
  558. resizePopupDelayed();
  559. let body = win.document.body;
  560. body.removeAttribute('data-resize-popup');
  561. let mutationObserver =
  562. new win.MutationObserver(onResizeRequested);
  563. mutationObserver.observe(body, {
  564. attributes: true,
  565. attributeFilter: [ 'data-resize-popup' ]
  566. });
  567. };
  568. iframe.addEventListener('load', onPopupReady, true);
  569. };
  570. })();
  571. /******************************************************************************/
  572. (function () {
  573. let tbb = vAPI.toolbarButton;
  574. if (tbb.init !== null) {
  575. return;
  576. }
  577. tbb.codePath = 'legacy';
  578. tbb.viewId = tbb.id + '-panel';
  579. let styleSheetUri = null;
  580. let createToolbarButton = function (window) {
  581. let document = window.document;
  582. let toolbarButton = document.createElement('toolbarbutton');
  583. toolbarButton.setAttribute('id', tbb.id);
  584. // type = panel would be more accurate, but doesn't look as good
  585. toolbarButton.setAttribute('type', 'menu');
  586. toolbarButton.setAttribute('removable', 'true');
  587. toolbarButton.setAttribute('class', 'toolbarbutton-1 '
  588. +'chromeclass-toolbar-additional');
  589. toolbarButton.setAttribute('label', tbb.label);
  590. toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext);
  591. let toolbarButtonPanel = document.createElement('panel');
  592. // NOTE: Setting level to parent breaks the popup for PaleMoon under
  593. // linux (mouse pointer misaligned with content). For some reason.
  594. // eMatrix: TODO check if it's still true
  595. // toolbarButtonPanel.setAttribute('level', 'parent');
  596. tbb.populatePanel(document, toolbarButtonPanel);
  597. toolbarButtonPanel.addEventListener('popupshowing',
  598. tbb.onViewShowing);
  599. toolbarButtonPanel.addEventListener('popuphiding',
  600. tbb.onViewHiding);
  601. toolbarButton.appendChild(toolbarButtonPanel);
  602. toolbarButtonPanel.setAttribute('tooltiptext', '');
  603. return toolbarButton;
  604. };
  605. let addLegacyToolbarButton = function (window) {
  606. // eMatrix's stylesheet lazily added.
  607. if (styleSheetUri === null) {
  608. var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
  609. .getService(Ci.nsIStyleSheetService);
  610. styleSheetUri = Services.io
  611. .newURI(vAPI.getURL("css/legacy-toolbar-button.css"),
  612. null, null);
  613. // Register global so it works in all windows, including palette
  614. if (!sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) {
  615. sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET);
  616. }
  617. }
  618. let document = window.document;
  619. // https://github.com/gorhill/uMatrix/issues/357
  620. // Already installed?
  621. if (document.getElementById(tbb.id) !== null) {
  622. return;
  623. }
  624. let toolbox = document.getElementById('navigator-toolbox')
  625. || document.getElementById('mail-toolbox');
  626. if (toolbox === null) {
  627. return;
  628. }
  629. let toolbarButton = createToolbarButton(window);
  630. let palette = toolbox.palette;
  631. if (palette && palette.querySelector('#' + tbb.id) === null) {
  632. palette.appendChild(toolbarButton);
  633. }
  634. // Find the place to put the button.
  635. // Pale Moon: `toolbox.externalToolbars` can be
  636. // undefined. Seen while testing popup test number 3:
  637. // http://raymondhill.net/ublock/popup.html
  638. let toolbars = toolbox.externalToolbars
  639. ? toolbox.externalToolbars.slice()
  640. : [];
  641. for (let child of toolbox.children) {
  642. if (child.localName === 'toolbar') {
  643. toolbars.push(child);
  644. }
  645. }
  646. for (let toolbar of toolbars) {
  647. let currentsetString = toolbar.getAttribute('currentset');
  648. if (!currentsetString) {
  649. continue;
  650. }
  651. let currentset = currentsetString.split(/\s*,\s*/);
  652. let index = currentset.indexOf(tbb.id);
  653. if (index === -1) {
  654. continue;
  655. }
  656. // This can occur with Pale Moon:
  657. // "TypeError: toolbar.insertItem is not a function"
  658. if (typeof toolbar.insertItem !== 'function') {
  659. continue;
  660. }
  661. // Found our button on this toolbar - but where on it?
  662. let before = null;
  663. for (let i=index+1; i<currentset.length; ++i) {
  664. // The [id=...] notation doesn't work on
  665. // space elements as they get a random ID each session
  666. // (or something like that)
  667. // https://gitlab.com/vannilla/ematrix/issues/5
  668. // https://gitlab.com/vannilla/ematrix/issues/6
  669. // Based on JustOff's snippet from the Pale Moon
  670. // forum. It was reorganized because I find it
  671. // more readable like this, but he did most of the
  672. // work.
  673. let space = /^(spring|spacer|separator)$/.exec(currentset[i]);
  674. if (space !== null) {
  675. let elems = toolbar.querySelectorAll('toolbar'+space[1]);
  676. let count = currentset.slice(i-currentset.length)
  677. .filter(function (x) {return x == space[1];})
  678. .length;
  679. before =
  680. toolbar.querySelector('[id="'
  681. + elems[elems.length-count].id
  682. + '"]');
  683. } else {
  684. before = toolbar.querySelector('[id="'+currentset[i]+'"]');
  685. }
  686. if ( before !== null ) {
  687. break;
  688. }
  689. }
  690. toolbar.insertItem(tbb.id, before);
  691. break;
  692. }
  693. // https://github.com/gorhill/uBlock/issues/763
  694. // We are done if our toolbar button is already installed
  695. // in one of the toolbar.
  696. if (palette !== null && toolbarButton.parentElement !== palette) {
  697. return;
  698. }
  699. // No button yet so give it a default location. If forcing
  700. // the button, just put in in the palette rather than on
  701. // any specific toolbar (who knows what toolbars will be
  702. // available or visible!)
  703. let navbar = document.getElementById('nav-bar');
  704. if (navbar !== null
  705. && !vAPI.localStorage.getBool('legacyToolbarButtonAdded')) {
  706. // https://github.com/gorhill/uBlock/issues/264
  707. // Find a child customizable palette, if any.
  708. navbar = navbar.querySelector('.customization-target') || navbar;
  709. navbar.appendChild(toolbarButton);
  710. navbar.setAttribute('currentset', navbar.currentSet);
  711. document.persist(navbar.id, 'currentset');
  712. vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true');
  713. }
  714. };
  715. let canAddLegacyToolbarButton = function (window) {
  716. let document = window.document;
  717. if (!document
  718. || document.readyState !== 'complete'
  719. || document.getElementById('nav-bar') === null) {
  720. return false;
  721. }
  722. let toolbox = document.getElementById('navigator-toolbox')
  723. || document.getElementById('mail-toolbox');
  724. return toolbox !== null && !!toolbox.palette;
  725. };
  726. let onPopupCloseRequested = function ({target}) {
  727. let document = target.ownerDocument;
  728. if (!document) {
  729. return;
  730. }
  731. let toolbarButtonPanel = document.getElementById(tbb.viewId);
  732. if (toolbarButtonPanel === null) {
  733. return;
  734. }
  735. // `hidePopup` reported as not existing while testing
  736. // legacy button on FF 41.0.2.
  737. // https://bugzilla.mozilla.org/show_bug.cgi?id=1151796
  738. if (typeof toolbarButtonPanel.hidePopup === 'function') {
  739. toolbarButtonPanel.hidePopup();
  740. }
  741. };
  742. let shutdown = function () {
  743. for (let win of vAPI.window.getWindows()) {
  744. let toolbarButton = win.document.getElementById(tbb.id);
  745. if (toolbarButton) {
  746. toolbarButton.parentNode.removeChild(toolbarButton);
  747. }
  748. }
  749. if (styleSheetUri !== null) {
  750. var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
  751. .getService(Ci.nsIStyleSheetService);
  752. if (sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) {
  753. sss.unregisterSheet(styleSheetUri, sss.AUTHOR_SHEET);
  754. }
  755. styleSheetUri = null;
  756. }
  757. vAPI.messaging.globalMessageManager
  758. .removeMessageListener(location.host + ':closePopup',
  759. onPopupCloseRequested);
  760. };
  761. tbb.attachToNewWindow = function (win) {
  762. vAPI.deferUntil(canAddLegacyToolbarButton.bind(null, win),
  763. addLegacyToolbarButton.bind(null, win));
  764. };
  765. tbb.init = function () {
  766. vAPI.messaging.globalMessageManager
  767. .addMessageListener(location.host + ':closePopup',
  768. onPopupCloseRequested);
  769. vAPI.addCleanUpTask(shutdown);
  770. };
  771. })();
  772. // No toolbar button.
  773. (function () {
  774. // Just to ensure the number of cleanup tasks is as expected: toolbar
  775. // button code is one single cleanup task regardless of platform.
  776. // eMatrix: might not be needed anymore
  777. if (vAPI.toolbarButton.init === null) {
  778. vAPI.addCleanUpTask(function(){});
  779. }
  780. })();
  781. if (vAPI.toolbarButton.init !== null) {
  782. vAPI.toolbarButton.init();
  783. }
  784. let optionsObserver = (function () {
  785. let addonId = 'eMatrix@vannilla.org';
  786. let commandHandler = function () {
  787. switch (this.id) {
  788. case 'showDashboardButton':
  789. vAPI.tabs.open({
  790. url: 'dashboard.html',
  791. index: -1,
  792. });
  793. break;
  794. case 'showLoggerButton':
  795. vAPI.tabs.open({
  796. url: 'logger-ui.html',
  797. index: -1,
  798. });
  799. break;
  800. default:
  801. break;
  802. }
  803. };
  804. let setupOptionsButton = function (doc, id) {
  805. let button = doc.getElementById(id);
  806. if (button === null) {
  807. return;
  808. }
  809. button.addEventListener('command', commandHandler);
  810. button.label = vAPI.i18n(id);
  811. };
  812. let setupOptionsButtons = function (doc) {
  813. setupOptionsButton(doc, 'showDashboardButton');
  814. setupOptionsButton(doc, 'showLoggerButton');
  815. };
  816. let observer = {
  817. observe: function (doc, topic, id) {
  818. if (id !== addonId) {
  819. return;
  820. }
  821. setupOptionsButtons(doc);
  822. }
  823. };
  824. var canInit = function() {
  825. // https://github.com/gorhill/uBlock/issues/948
  826. // Older versions of Firefox can throw here when looking
  827. // up `currentURI`.
  828. try {
  829. let tabBrowser = vAPI.tabs.manager.currentBrowser();
  830. return tabBrowser
  831. && tabBrowser.currentURI
  832. && tabBrowser.currentURI.spec === 'about:addons'
  833. && tabBrowser.contentDocument
  834. && tabBrowser.contentDocument.readyState === 'complete';
  835. } catch (ex) {
  836. // Ignore
  837. }
  838. };
  839. // Manually add the buttons if the `about:addons` page is
  840. // already opened.
  841. let init = function () {
  842. if (canInit()) {
  843. setupOptionsButtons(vAPI.tabs.manager
  844. .currentBrowser().contentDocument);
  845. }
  846. };
  847. let unregister = function () {
  848. Services.obs.removeObserver(observer, 'addon-options-displayed');
  849. };
  850. let register = function () {
  851. Services.obs.addObserver(observer,
  852. 'addon-options-displayed',
  853. false);
  854. vAPI.addCleanUpTask(unregister);
  855. vAPI.deferUntil(canInit, init, { next: 463 });
  856. };
  857. return {
  858. register: register,
  859. unregister: unregister
  860. };
  861. })();
  862. optionsObserver.register();
  863. vAPI.onLoadAllCompleted = function() {
  864. // This is called only once, when everything has been loaded
  865. // in memory after the extension was launched. It can be used
  866. // to inject content scripts in already opened web pages, to
  867. // remove whatever nuisance could make it to the web pages
  868. // before uBlock was ready.
  869. for (let browser of vAPI.tabs.manager.browsers()) {
  870. browser.messageManager
  871. .sendAsyncMessage(location.host + '-load-completed');
  872. }
  873. };
  874. // Likelihood is that we do not have to punycode: given punycode overhead,
  875. // it's faster to check and skip than do it unconditionally all the time.
  876. var punycodeHostname = Punycode.toASCII;
  877. var isNotASCII = /[^\x21-\x7F]/;
  878. vAPI.punycodeHostname = function (hostname) {
  879. return isNotASCII.test(hostname)
  880. ? punycodeHostname(hostname)
  881. : hostname;
  882. };
  883. vAPI.punycodeURL = function (url) {
  884. if (isNotASCII.test(url)) {
  885. return Services.io.newURI(url, null, null).asciiSpec;
  886. }
  887. return url;
  888. };
  889. })();