You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
7.0 KiB

  1. /*******************************************************************************
  2. µBlock - a browser extension to block requests.
  3. Copyright (C) 2014 The µBlock authors
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see {http://www.gnu.org/licenses/}.
  14. Home: https://github.com/gorhill/uBlock
  15. */
  16. /* global Services, Components, XPCOMUtils */
  17. 'use strict';
  18. /******************************************************************************/
  19. this.EXPORTED_SYMBOLS = ['contentPolicy', 'docObserver'];
  20. const {interfaces: Ci, utils: Cu} = Components;
  21. let appName;
  22. try { throw new Error; } catch (ex) {
  23. appName = ex.fileName.match(/:\/\/([^\/]+)/)[1];
  24. }
  25. Cu['import']('resource://gre/modules/Services.jsm');
  26. Cu['import']('resource://gre/modules/XPCOMUtils.jsm');
  27. // Cu['import']('resource://gre/modules/devtools/Console.jsm');
  28. /******************************************************************************/
  29. let getMessager = function(win) {
  30. try {
  31. // e10s
  32. return win
  33. .QueryInterface(Ci.nsIInterfaceRequestor)
  34. .getInterface(Ci.nsIWebNavigation)
  35. .QueryInterface(Ci.nsIDocShellTreeItem)
  36. .rootTreeItem
  37. .QueryInterface(Ci.nsIInterfaceRequestor)
  38. .getInterface(Ci.nsIContentFrameMessageManager);
  39. } catch (ex) {
  40. return win
  41. .QueryInterface(Ci.nsIInterfaceRequestor)
  42. .getInterface(Ci.nsIWebNavigation)
  43. .QueryInterface(Ci.nsIDocShell)
  44. .QueryInterface(Ci.nsIInterfaceRequestor)
  45. .getInterface(Ci.nsIContentFrameMessageManager);
  46. }
  47. };
  48. /******************************************************************************/
  49. let contentPolicy = {
  50. classDescription: 'content-policy implementation for ' + appName,
  51. classID: Components.ID('{e6d173c8-8dbf-4189-a6fd-189e8acffd27}'),
  52. contractID: '@' + appName + '/content-policy;1',
  53. ACCEPT: Ci.nsIContentPolicy.ACCEPT,
  54. REJECT: Ci.nsIContentPolicy.REJECT_REQUEST,
  55. requestMessageName: appName + ':onBeforeRequest',
  56. get componentRegistrar() {
  57. return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
  58. },
  59. get categoryManager() {
  60. return Components.classes['@mozilla.org/categorymanager;1']
  61. .getService(Ci.nsICategoryManager);
  62. },
  63. QueryInterface: XPCOMUtils.generateQI([
  64. Ci.nsIFactory,
  65. Ci.nsIContentPolicy,
  66. Ci.nsISupportsWeakReference
  67. ]),
  68. createInstance: function(outer, iid) {
  69. if (outer) {
  70. throw Components.results.NS_ERROR_NO_AGGREGATION;
  71. }
  72. return this.QueryInterface(iid);
  73. },
  74. register: function() {
  75. this.componentRegistrar.registerFactory(
  76. this.classID,
  77. this.classDescription,
  78. this.contractID,
  79. this
  80. );
  81. this.categoryManager.addCategoryEntry(
  82. 'content-policy',
  83. this.contractID,
  84. this.contractID,
  85. false,
  86. true
  87. );
  88. },
  89. unregister: function() {
  90. this.componentRegistrar.unregisterFactory(this.classID, this);
  91. this.categoryManager.deleteCategoryEntry(
  92. 'content-policy',
  93. this.contractID,
  94. false
  95. );
  96. },
  97. shouldLoad: function(type, location, origin, context) {
  98. if (!context || !/^https?$/.test(location.scheme)) {
  99. return this.ACCEPT;
  100. }
  101. let win = type === 6
  102. ? context.contentWindow || context
  103. : (context.ownerDocument || context).defaultView;
  104. if (!win) {
  105. return this.ACCEPT;
  106. }
  107. let result = getMessager(win).sendSyncMessage(this.requestMessageName, {
  108. url: location.spec,
  109. type: type,
  110. tabId: -1, // determined in background script
  111. frameId: type === 6 ? -1 : (win === win.top ? 0 : 1),
  112. parentFrameId: win === win.top ? -1 : 0
  113. })[0];
  114. return result === true ? this.REJECT : this.ACCEPT;
  115. }/*,
  116. shouldProcess: function() {
  117. return this.ACCEPT;
  118. }*/
  119. };
  120. /******************************************************************************/
  121. let docObserver = {
  122. contentBaseURI: 'chrome://' + appName + '/content/',
  123. initContext: function(win, sandbox) {
  124. let messager = getMessager(win);
  125. if (sandbox) {
  126. win = Cu.Sandbox([win], {
  127. sandboxPrototype: win,
  128. wantComponents: false,
  129. wantXHRConstructor: false
  130. });
  131. win.self = win;
  132. // anonymous function needs to be used here
  133. win.injectScript = Cu.exportFunction(
  134. function(script, evalCode) {
  135. if (evalCode) {
  136. Cu.evalInSandbox(script, win);
  137. return;
  138. }
  139. Services.scriptloader.loadSubScript(
  140. docObserver.contentBaseURI + script,
  141. win
  142. );
  143. },
  144. win
  145. );
  146. }
  147. win.sendAsyncMessage = messager.sendAsyncMessage;
  148. win.addMessageListener = messager.ublock_addMessageListener;
  149. win.removeMessageListener = messager.ublock_removeMessageListener;
  150. return win;
  151. },
  152. register: function() {
  153. Services.obs.addObserver(this, 'document-element-inserted', false);
  154. },
  155. unregister: function() {
  156. Services.obs.removeObserver(this, 'document-element-inserted');
  157. },
  158. observe: function(doc) {
  159. let win = doc.defaultView;
  160. if (!win) {
  161. return;
  162. }
  163. if (!/^https?:$/.test(win.location.protocol)) {
  164. if (win.location.protocol === 'chrome:'
  165. && win.location.host === appName) {
  166. this.initContext(win);
  167. }
  168. return;
  169. }
  170. let lss = Services.scriptloader.loadSubScript;
  171. win = this.initContext(win, true);
  172. lss(this.contentBaseURI + 'js/vapi-client.js', win);
  173. lss(this.contentBaseURI + 'js/contentscript-start.js', win);
  174. let docReady = function(e) {
  175. this.removeEventListener(e.type, docReady, true);
  176. lss(docObserver.contentBaseURI + 'js/contentscript-end.js', win);
  177. };
  178. doc.addEventListener('DOMContentLoaded', docReady, true);
  179. }
  180. };
  181. /******************************************************************************/
  182. contentPolicy.register();
  183. docObserver.register();
  184. /******************************************************************************/