diff options
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/child/elemHide.js')
-rw-r--r-- | data/extensions/spyblock@gnu.org/lib/child/elemHide.js | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/child/elemHide.js b/data/extensions/spyblock@gnu.org/lib/child/elemHide.js new file mode 100644 index 0000000..988adee --- /dev/null +++ b/data/extensions/spyblock@gnu.org/lib/child/elemHide.js @@ -0,0 +1,403 @@ +/* + * This file is part of Adblock Plus <https://adblockplus.org/>, + * Copyright (C) 2006-2017 eyeo GmbH + * + * Adblock Plus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * Adblock Plus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * @fileOverview Serves CSS for element hiding and processes hits. + */ + +try +{ + // Hack: SDK loader masks our Components object with a getter. + let proto = Object.getPrototypeOf(this); + let property = Object.getOwnPropertyDescriptor(proto, "Components"); + if (property && property.get) + delete proto.Components; +} +catch (e) +{ + Cu.reportError(e); +} + +let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let {shouldAllowAsync} = require("child/contentPolicy"); +let {getFrames, isPrivate, getRequestWindow} = require("child/utils"); +let {RequestNotifier} = require("child/requestNotifier"); +let {port} = require("messaging"); +let {Utils} = require("utils"); + +const notImplemented = () => Cr.NS_ERROR_NOT_IMPLEMENTED; + +/** + * about: URL module used to count hits. + * @class + */ +let AboutHandler = +{ + classID: Components.ID("{55fb7be0-1dd2-11b2-98e6-9e97caf8ba67}"), + classDescription: "Element hiding hit registration protocol handler", + aboutPrefix: "abp-elemhide", + + /** + * Registers handler on startup. + */ + init: function() + { + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory(this.classID, this.classDescription, + "@mozilla.org/network/protocol/about;1?what=" + this.aboutPrefix, this); + onShutdown.add(function() + { + registrar.unregisterFactory(this.classID, this); + }.bind(this)); + }, + + // + // Factory implementation + // + + createInstance: function(outer, iid) + { + if (outer != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + + return this.QueryInterface(iid); + }, + + // + // About module implementation + // + + getURIFlags: function(uri) + { + return Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT; + }, + + newChannel: function(uri, loadInfo) + { + let match = /\?hit(\d+)$/.exec(uri.path); + if (match) + return new HitRegistrationChannel(uri, loadInfo, match[1]); + + match = /\?css(?:=(.*?))?(&specificonly)?$/.exec(uri.path); + if (match) + { + return new StyleDataChannel(uri, loadInfo, + match[1] ? decodeURIComponent(match[1]) : null, !!match[2]); + } + + throw Cr.NS_ERROR_FAILURE; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule]) +}; +AboutHandler.init(); + +/** + * Base class for channel implementations, subclasses usually only need to + * override BaseChannel._getResponse() method. + * @constructor + */ +function BaseChannel(uri, loadInfo) +{ + this.URI = this.originalURI = uri; + this.loadInfo = loadInfo; +} +BaseChannel.prototype = { + URI: null, + originalURI: null, + contentCharset: "utf-8", + contentLength: 0, + contentType: null, + owner: Utils.systemPrincipal, + securityInfo: null, + notificationCallbacks: null, + loadFlags: 0, + loadGroup: null, + name: null, + status: Cr.NS_OK, + + _getResponse: notImplemented, + + _checkSecurity: function() + { + if (!this.loadInfo.triggeringPrincipal.equals(Utils.systemPrincipal)) + throw Cr.NS_ERROR_FAILURE; + }, + + asyncOpen: function(listener, context) + { + Promise.resolve(this._getResponse()).then(data => + { + let stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(data, data.length); + + try + { + listener.onStartRequest(this, context); + } + catch(e) + { + // Listener failing isn't our problem + } + + try + { + listener.onDataAvailable(this, context, stream, 0, stream.available()); + } + catch(e) + { + // Listener failing isn't our problem + } + + try + { + listener.onStopRequest(this, context, Cr.NS_OK); + } + catch(e) + { + // Listener failing isn't our problem + } + }); + }, + + asyncOpen2: function(listener) + { + this._checkSecurity(); + this.asyncOpen(listener, null); + }, + + open: function() + { + let data = this._getResponse(); + if (typeof data.then == "function") + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + + let stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(data, data.length); + return stream; + }, + + open2: function() + { + this._checkSecurity(); + return this.open(); + }, + + isPending: () => false, + cancel: notImplemented, + suspend: notImplemented, + resume: notImplemented, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest]) +}; + +/** + * Channel returning CSS data for the global as well as site-specific stylesheet. + * @constructor + */ +function StyleDataChannel(uri, loadInfo, domain, specificOnly) +{ + BaseChannel.call(this, uri, loadInfo); + this._domain = domain; + this._specificOnly = specificOnly; +} +StyleDataChannel.prototype = { + __proto__: BaseChannel.prototype, + contentType: "text/css", + _domain: null, + + _getResponse: function() + { + function escapeChar(match) + { + return "\\" + match.charCodeAt(0).toString(16) + " "; + } + + // Would be great to avoid sync messaging here but nsIStyleSheetService + // insists on opening channels synchronously. + let [selectors, keys] = (this._domain ? + port.emitSync("getSelectorsForDomain", [this._domain, this._specificOnly]) : + port.emitSync("getUnconditionalSelectors")); + + let cssPrefix = "{-moz-binding: url(about:abp-elemhide?hit"; + let cssSuffix = "#dummy) !important;}\n"; + let result = []; + + for (let i = 0; i < selectors.length; i++) + { + let selector = selectors[i]; + let key = keys[i]; + result.push(selector.replace(/[^\x01-\x7F]/g, escapeChar), + cssPrefix, key, cssSuffix); + } + + return result.join(""); + } +}; + +/** + * Channel returning data for element hiding hits. + * @constructor + */ +function HitRegistrationChannel(uri, loadInfo, key) +{ + BaseChannel.call(this, uri, loadInfo); + this.key = key; +} +HitRegistrationChannel.prototype = { + __proto__: BaseChannel.prototype, + key: null, + contentType: "text/xml", + + _getResponse: function() + { + let window = getRequestWindow(this); + port.emitWithResponse("registerElemHideHit", { + key: this.key, + frames: getFrames(window), + isPrivate: isPrivate(window) + }).then(hit => + { + if (hit) + RequestNotifier.addNodeData(window.document, window.top, hit); + }); + return "<bindings xmlns='http://www.mozilla.org/xbl'/>"; + } +}; + +let observer = { + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIObserver, Ci.nsISupportsWeakReference + ]), + + topic: "document-element-inserted", + styleURL: Utils.makeURI("about:abp-elemhide?css"), + sheet: null, + + init: function() + { + Services.obs.addObserver(this, this.topic, true); + onShutdown.add(() => + { + Services.obs.removeObserver(this, this.topic); + }); + + port.on("elemhideupdate", () => + { + this.sheet = null; + }); + }, + + observe: function(subject, topic, data) + { + if (topic != this.topic) + return; + + let window = subject.defaultView; + if (!window) + { + // This is typically XBL bindings and SVG images, but also real + // documents occasionally - probably due to speculative loading? + return; + } + let type = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .itemType; + if (type != Ci.nsIDocShellTreeItem.typeContent) + return; + + port.emitWithResponse("elemhideEnabled", { + frames: getFrames(window), + isPrivate: isPrivate(window) + }).then(({ + enabled, contentType, docDomain, thirdParty, location, filter, + filterType + }) => + { + if (Cu.isDeadWrapper(window)) + { + // We are too late, the window is gone already. + return; + } + + if (enabled) + { + let utils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + // If we have a filter hit at this point then it must be a $generichide + // filter - apply only specific element hiding filters. + let specificOnly = !!filter; + if (!specificOnly) + { + if (!this.sheet) + { + this.sheet = Utils.styleService.preloadSheet(this.styleURL, + Ci.nsIStyleSheetService.USER_SHEET); + } + + try + { + utils.addSheet(this.sheet, Ci.nsIStyleSheetService.USER_SHEET); + } + catch (e) + { + // Ignore NS_ERROR_ILLEGAL_VALUE - it will be thrown if we try to add + // the stylesheet multiple times to the same document (the observer + // will be notified twice for some documents). + if (e.result != Cr.NS_ERROR_ILLEGAL_VALUE) + throw e; + } + } + + let host = window.location.hostname; + if (host) + { + try + { + let suffix = "=" + encodeURIComponent(host); + if (specificOnly) + suffix += "&specificonly"; + utils.loadSheetUsingURIString(this.styleURL.spec + suffix, + Ci.nsIStyleSheetService.USER_SHEET); + } + catch (e) + { + // Ignore NS_ERROR_ILLEGAL_VALUE - it will be thrown if we try to add + // the stylesheet multiple times to the same document (the observer + // will be notified twice for some documents). + if (e.result != Cr.NS_ERROR_ILLEGAL_VALUE) + throw e; + } + } + } + + if (filter) + { + RequestNotifier.addNodeData(window.document, window.top, { + contentType, docDomain, thirdParty, location, filter, filterType + }); + } + }); + } +}; +observer.init(); |