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

Delta Between Two Patch Sets: safari/contentBlocking.js

Issue 29340571: Issue 3687 - Add experimental support for Safari content blockers (Closed)
Left Patch Set: Created April 19, 2016, 1:59 p.m.
Right Patch Set: Addressed Nits Created May 18, 2016, 11:30 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « popup.js ('k') | safari/ext/background.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 /** @module contentBlocking */ 18 /** @module contentBlocking */
19 19
20 "use strict"; 20 "use strict";
21 21
22 let {Prefs} = require("prefs"); 22 let {Prefs} = require("prefs");
23 let {ContentBlockerList} = require("abp2blocklist"); 23 let {ContentBlockerList} = require("abp2blocklist");
24 let {FilterStorage} = require("filterStorage"); 24 let {FilterStorage} = require("filterStorage");
25 let {FilterNotifier} = require("filterNotifier"); 25 let {FilterNotifier} = require("filterNotifier");
26 let {port} = require("messaging"); 26 let {port} = require("messaging");
27 27
28 let contentBlockingSupported = "setContentBlocker" in safari.extension; 28 let contentBlockingSupported = "setContentBlocker" in safari.extension;
29 let legacyApiSupported = "onbeforeload" in Element.prototype; 29 let legacyAPISupported = new Promise(resolve =>
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.
30 let contentBlockingActive = contentBlockingSupported && !legacyApiSupported; 30 {
31 let afterContentBlockingFinished; 31 function onLegacyAPISupported(msg, sender)
Sebastian Noack 2016/05/12 11:12:23 If I understand the logic correctly, this variable
kzar 2016/05/17 15:15:39 Done.
32 {
33 port.off("safari.legacyAPISupported", onLegacyAPISupported);
34 resolve(msg.legacyAPISupported);
35 }
36 port.on("safari.legacyAPISupported", onLegacyAPISupported);
37 });
38 let contentBlockingActive = false;
39 let afterContentBlockingFinished = null;
32 let contentBlockListDirty = true; 40 let contentBlockListDirty = true;
33 let lastSetContentBlockerResult; 41 let lastSetContentBlockerError;
34 42
35 function clearBlockCounters() 43 function clearBlockCounters()
36 { 44 {
37 ext.pages.query({}, pages => 45 ext.pages.query({}, pages =>
38 { 46 {
39 for (let page of pages) 47 for (let page of pages)
40 page.browserAction.setBadge(); 48 page.browserAction.setBadge();
41 }); 49 });
42 } 50 }
43 51
44 function setContentBlocker(callback) 52 function setContentBlocker(callback)
45 { 53 {
46 // setContentBlocker returns null if given the same blocklist as last time, 54 // When given the same rules as last time setContentBlocker will always give
47 // even when there was an error. (It's also wasteful to re-generate the 55 // null (success) to the callback, even when there was actually an error. We
48 // blocklist when nothing has changed!) 56 // cache the last result therefore so that we can provide a consistent result
57 // and also to avoid wastefully regenerating an identical blocklist.
49 if (!contentBlockListDirty) 58 if (!contentBlockListDirty)
50 { 59 {
51 callback(lastSetContentBlockerResult); 60 callback(lastSetContentBlockerError);
52 return; 61 return;
53 } 62 }
54 63
55 let contentBlockerList = new ContentBlockerList(); 64 let contentBlockerList = new ContentBlockerList();
56 for (let subscription of FilterStorage.subscriptions) 65 for (let subscription of FilterStorage.subscriptions)
57 if (!subscription.disabled) 66 if (!subscription.disabled)
58 for (let filter of subscription.filters) 67 for (let filter of subscription.filters)
59 contentBlockerList.addFilter(filter); 68 contentBlockerList.addFilter(filter);
60 69
70 contentBlockListDirty = false;
61 safari.extension.setContentBlocker( 71 safari.extension.setContentBlocker(
62 // setContentBlocker seems to work unreliably in Safari 9 unless the the 72 // There is a strange bug in setContentBlocker for Safari 9 where if both
63 // given rules are converted to JSON first. (An error is sometimes thrown: 73 // the callback parameter is provided and the rules aren't converted to a
64 // "Extension compilation failed: Failed to parse the JSON String.") 74 // JSON string it fails. Worse still it actually performs the callback twice
75 // too, firstly with an empty string and then with an Error:
76 // "Extension compilation failed: Failed to parse the JSON String."
77 // To mitigate this we convert the rules to JSON here and also ignore
78 // callback values of "". (Usually the callback is performed with either
79 // null for success or an Error on failure.)
80 // Bug #26322821 filed on bugreport.apple.com
65 JSON.stringify(contentBlockerList.generateRules()), 81 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.
66 function (result) 82 function(error)
67 { 83 {
68 contentBlockListDirty = false; 84 if (error == "")
69 lastSetContentBlockerResult = result; 85 return;
70 callback(result); 86
87 lastSetContentBlockerError = error;
88 callback(error);
71 } 89 }
72 ); 90 );
73 } 91 }
74 92
75 function updateContentBlocker(onStartup) 93 function updateContentBlocker(isStartup, legacyAPISupported)
Sebastian Noack 2016/05/12 11:12:23 Nit: onStartup -> isStartup, since this value indi
kzar 2016/05/17 15:15:39 Done.
76 { 94 {
77 afterContentBlockingFinished = new Promise(resolve => 95 afterContentBlockingFinished = new Promise(resolve =>
78 { 96 {
79 setContentBlocker(result => 97 setContentBlocker(error =>
80 { 98 {
81 if (result instanceof Error) 99 if (error instanceof Error)
82 { 100 {
83 let suppressErrorMessage = false; 101 let suppressErrorMessage = false;
84 102
85 // If the content blocking API fails the first time it's used the 103 // If the content blocking API fails the first time it's used the
86 // legacy blocking API (if available) won't have been disabled. 104 // legacy blocking API (if available) won't have been disabled.
87 if (!contentBlockingActive && legacyApiSupported) 105 if (!contentBlockingActive && legacyAPISupported)
88 { 106 {
89 Prefs["safariContentBlocker"] = false; 107 Prefs.safariContentBlocker = false;
90 Prefs.on("safariContentBlocker", onSafariContentBlocker);
91 // If content blocking failed on startup and we're switching back to 108 // If content blocking failed on startup and we're switching back to
92 // the legacy API anyway we don't need to show an error message. 109 // the legacy API anyway we don't need to show an error message.
93 if (onStartup) 110 if (isStartup)
94 suppressErrorMessage = true; 111 suppressErrorMessage = true;
95 } 112 }
96 113
97 if (!suppressErrorMessage) 114 if (!suppressErrorMessage)
98 alert(result.message); 115 alert(error.message);
99 } 116 }
100 else if (!contentBlockingActive) 117 else if (!contentBlockingActive)
101 { 118 {
102 contentBlockingActive = true; 119 contentBlockingActive = true;
103 clearBlockCounters(); 120 clearBlockCounters();
104 } 121 }
105 122
106 resolve(contentBlockingActive); 123 resolve(contentBlockingActive);
107 afterContentBlockingFinished = undefined; 124 afterContentBlockingFinished = null;
108 }); 125 });
109 }); 126 });
110 } 127 }
111 128
112 function onSafariContentBlocker()
113 {
114 Prefs.off("safariContentBlocker", onSafariContentBlocker);
115 updateContentBlocker();
116 }
117
118 if (contentBlockingSupported) 129 if (contentBlockingSupported)
119 { 130 {
120 Promise.all([Prefs.untilLoaded, FilterNotifier.once("load")]).then(() => 131 Promise.all([Prefs.untilLoaded,
132 FilterNotifier.once("load"),
133 legacyAPISupported]).then(resolvedValues =>
121 { 134 {
122 // If this version of Safari only supports the new API then let's make sure 135 let legacyAPISupported = resolvedValues[2];
123 // the safariContentBlocker preference is set to true for consistency. 136 if (!legacyAPISupported)
124 if (contentBlockingActive) 137 Prefs.safariContentBlocker = true;
125 Prefs["safariContentBlocker"] = true;
126 138
127 if (Prefs["safariContentBlocker"]) 139 if (Prefs.safariContentBlocker)
Sebastian Noack 2016/05/12 11:12:23 Nit: Prefs.safariContentBlocker
kzar 2016/05/17 15:15:38 Done.
128 updateContentBlocker(true); 140 updateContentBlocker(true, legacyAPISupported);
129 else
130 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.
131 141
132 for (let action of ["load", "subscription.added", "subscription.removed", 142 Prefs.on("safariContentBlocker", () =>
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.
133 "subscription.updated", "subscription.disabled", 143 {
134 "filter.added", "filter.removed", "filter.disabled"]) 144 if (!contentBlockingActive && Prefs.safariContentBlocker)
135 FilterNotifier.on(action, () => 145 updateContentBlocker(false, legacyAPISupported);
136 { 146 });
137 contentBlockListDirty = true; 147
138 if (contentBlockingActive) 148 FilterNotifier.on("filter.behaviorChanged", () =>
139 updateContentBlocker(); 149 {
140 }); 150 contentBlockListDirty = true;
151 if (contentBlockingActive)
152 updateContentBlocker(false, legacyAPISupported);
153 });
141 }); 154 });
142 } 155 }
143 156
144 port.on("safari.contentBlockingActive", function(msg, sender) 157 port.on("safari.contentBlockingActive", (msg, sender) =>
145 { 158 {
146 if (!contentBlockingActive && afterContentBlockingFinished) 159 if (!contentBlockingActive && afterContentBlockingFinished)
147 return afterContentBlockingFinished; 160 return afterContentBlockingFinished;
148 return contentBlockingActive; 161 return contentBlockingActive;
149 }); 162 });
150
LEFTRIGHT

Powered by Google App Engine
This is Rietveld