vapi-background.js 42 KB

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