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

Unified Diff: safari/contentBlocking.js

Issue 29340571: Issue 3687 - Add experimental support for Safari content blockers (Closed)
Patch Set: Created April 19, 2016, 1:59 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: safari/contentBlocking.js
diff --git a/safari/contentBlocking.js b/safari/contentBlocking.js
new file mode 100644
index 0000000000000000000000000000000000000000..d04b08a28649549e2577a109334fedb8b8a3c74e
--- /dev/null
+++ b/safari/contentBlocking.js
@@ -0,0 +1,150 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2016 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/>.
+ */
+
+/** @module contentBlocking */
+
+"use strict";
+
+let {Prefs} = require("prefs");
+let {ContentBlockerList} = require("abp2blocklist");
+let {FilterStorage} = require("filterStorage");
+let {FilterNotifier} = require("filterNotifier");
+let {port} = require("messaging");
+
+let contentBlockingSupported = "setContentBlocker" in safari.extension;
+let legacyApiSupported = "onbeforeload" in Element.prototype;
kzar 2016/04/19 14:03:49 I remember you said we shouldn't assume both onbef
Sebastian Noack 2016/05/12 11:12:23 You could detect the legacy API in the content scr
kzar 2016/05/17 15:15:39 Done.
+let contentBlockingActive = contentBlockingSupported && !legacyApiSupported;
+let afterContentBlockingFinished;
Sebastian Noack 2016/05/12 11:12:23 If I understand the logic correctly, this variable
kzar 2016/05/17 15:15:39 Done.
+let contentBlockListDirty = true;
+let lastSetContentBlockerResult;
+
+function clearBlockCounters()
+{
+ ext.pages.query({}, pages =>
+ {
+ for (let page of pages)
+ page.browserAction.setBadge();
+ });
+}
+
+function setContentBlocker(callback)
+{
+ // setContentBlocker returns null if given the same blocklist as last time,
+ // even when there was an error. (It's also wasteful to re-generate the
+ // blocklist when nothing has changed!)
+ if (!contentBlockListDirty)
+ {
+ callback(lastSetContentBlockerResult);
+ return;
+ }
+
+ let contentBlockerList = new ContentBlockerList();
+ for (let subscription of FilterStorage.subscriptions)
+ if (!subscription.disabled)
+ for (let filter of subscription.filters)
+ contentBlockerList.addFilter(filter);
+
+ safari.extension.setContentBlocker(
+ // setContentBlocker seems to work unreliably in Safari 9 unless the the
+ // given rules are converted to JSON first. (An error is sometimes thrown:
+ // "Extension compilation failed: Failed to parse the JSON String.")
+ JSON.stringify(contentBlockerList.generateRules()),
Sebastian Noack 2016/05/12 11:12:23 That needs to be tracked down properly. If there i
kzar 2016/05/17 15:15:39 OK, I'll try again to figure out what's going on.
+ function (result)
+ {
+ contentBlockListDirty = false;
+ lastSetContentBlockerResult = result;
+ callback(result);
+ }
+ );
+}
+
+function updateContentBlocker(onStartup)
Sebastian Noack 2016/05/12 11:12:23 Nit: onStartup -> isStartup, since this value indi
kzar 2016/05/17 15:15:39 Done.
+{
+ afterContentBlockingFinished = new Promise(resolve =>
+ {
+ setContentBlocker(result =>
+ {
+ if (result instanceof Error)
+ {
+ let suppressErrorMessage = false;
+
+ // If the content blocking API fails the first time it's used the
+ // legacy blocking API (if available) won't have been disabled.
+ if (!contentBlockingActive && legacyApiSupported)
+ {
+ Prefs["safariContentBlocker"] = false;
+ Prefs.on("safariContentBlocker", onSafariContentBlocker);
+ // If content blocking failed on startup and we're switching back to
+ // the legacy API anyway we don't need to show an error message.
+ if (onStartup)
+ suppressErrorMessage = true;
+ }
+
+ if (!suppressErrorMessage)
+ alert(result.message);
+ }
+ else if (!contentBlockingActive)
+ {
+ contentBlockingActive = true;
+ clearBlockCounters();
+ }
+
+ resolve(contentBlockingActive);
+ afterContentBlockingFinished = undefined;
+ });
+ });
+}
+
+function onSafariContentBlocker()
+{
+ Prefs.off("safariContentBlocker", onSafariContentBlocker);
+ updateContentBlocker();
+}
+
+if (contentBlockingSupported)
+{
+ Promise.all([Prefs.untilLoaded, FilterNotifier.once("load")]).then(() =>
+ {
+ // If this version of Safari only supports the new API then let's make sure
+ // the safariContentBlocker preference is set to true for consistency.
+ if (contentBlockingActive)
+ Prefs["safariContentBlocker"] = true;
+
+ if (Prefs["safariContentBlocker"])
Sebastian Noack 2016/05/12 11:12:23 Nit: Prefs.safariContentBlocker
kzar 2016/05/17 15:15:38 Done.
+ updateContentBlocker(true);
+ else
+ Prefs.on("safariContentBlocker", onSafariContentBlocker);
Sebastian Noack 2016/05/12 11:12:23 I wonder whether the logic wouldn't be simpler if
kzar 2016/05/17 15:15:40 Done.
+
+ for (let action of ["load", "subscription.added", "subscription.removed",
Sebastian Noack 2016/05/12 11:12:23 As usual, jsHydra will duplicate that array in the
kzar 2016/05/17 15:15:38 Done.
+ "subscription.updated", "subscription.disabled",
+ "filter.added", "filter.removed", "filter.disabled"])
+ FilterNotifier.on(action, () =>
+ {
+ contentBlockListDirty = true;
+ if (contentBlockingActive)
+ updateContentBlocker();
+ });
+ });
+}
+
+port.on("safari.contentBlockingActive", function(msg, sender)
+{
+ if (!contentBlockingActive && afterContentBlockingFinished)
+ return afterContentBlockingFinished;
+ return contentBlockingActive;
+});
+

Powered by Google App Engine
This is Rietveld