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

Delta Between Two Patch Sets: polyfill.js

Issue 30024555: Issue 7334 - Remove support for callbacks in API wrappers (Closed) Base URL: https://hg.adblockplus.org/adblockpluschrome/
Left Patch Set: Created March 6, 2019, 11 a.m.
Right Patch Set: Remove checks Created April 25, 2019, 9:04 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 | « no previous file | no next file » | 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-present eyeo GmbH 3 * Copyright (C) 2006-present 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 "use strict"; 18 "use strict";
19 19
20 { 20 {
21 const asyncAPIs = [ 21 let asyncAPIs = [
22 "browserAction.setIcon", 22 "browserAction.setIcon",
23 "browserAction.setBadgeText",
24 "browserAction.setBadgeBackgroundColor",
25 "browserAction.getPopup", 23 "browserAction.getPopup",
26 "contextMenus.removeAll", 24 "contextMenus.removeAll",
27 "devtools.panels.create", 25 "devtools.panels.create",
28 "notifications.clear", 26 "notifications.clear",
29 "notifications.create", 27 "notifications.create",
30 "runtime.getBrowserInfo", 28 "runtime.getBrowserInfo",
31 "runtime.openOptionsPage", 29 "runtime.openOptionsPage",
32 "runtime.sendMessage", 30 "runtime.sendMessage",
33 "runtime.setUninstallURL", 31 "runtime.setUninstallURL",
34 "storage.local.get", 32 "storage.local.get",
35 "storage.local.remove", 33 "storage.local.remove",
36 "storage.local.set", 34 "storage.local.set",
37 "storage.managed.get", 35 "storage.managed.get",
36 "tabs.captureVisibleTab",
38 "tabs.create", 37 "tabs.create",
39 "tabs.executeScript", 38 "tabs.executeScript",
40 "tabs.get", 39 "tabs.get",
41 "tabs.getCurrent", 40 "tabs.getCurrent",
42 "tabs.insertCSS", 41 "tabs.insertCSS",
43 "tabs.query", 42 "tabs.query",
44 "tabs.reload", 43 "tabs.reload",
45 "tabs.remove", 44 "tabs.remove",
46 "tabs.removeCSS", 45 "tabs.removeCSS",
47 "tabs.sendMessage", 46 "tabs.sendMessage",
48 "tabs.update", 47 "tabs.update",
49 "webNavigation.getAllFrames", 48 "webNavigation.getAllFrames",
50 "webRequest.handlerBehaviorChanged", 49 "webRequest.handlerBehaviorChanged",
51 "windows.create", 50 "windows.create",
52 "windows.update" 51 "windows.update"
53 ]; 52 ];
54 53
54 // Microsoft Edge (44.17763.1.0), Chrome (<= 66) and Opera (<= 54)
55 // don't accept passing a callback for
56 // browserAction.setBadgeText and browserAction.setBadgeBackgroundColor
57 const maybeAsyncAPIs = [
58 ["browserAction.setBadgeText", {text: ""}],
59 ["browserAction.setBadgeBackgroundColor", {color: [0, 0, 0, 0]}]
60 ];
61 let syncAPIs = [];
62
55 // Since we add a callback for all messaging API calls in our wrappers, 63 // Since we add a callback for all messaging API calls in our wrappers,
56 // Chrome assumes we're interested in the response; when there's no response, 64 // Chrome assumes we're interested in the response; when there's no response,
57 // it sets runtime.lastError 65 // it sets runtime.lastError
58 const portClosedBeforeResponseError = 66 const portClosedBeforeResponseError =
59 // Older versions of Chrome have a typo: 67 // Older versions of Chrome have a typo:
60 // https://crrev.com/c33f51726eacdcc1a487b21a13611f7eab580d6d 68 // https://crrev.com/c33f51726eacdcc1a487b21a13611f7eab580d6d
61 /^The message port closed before a res?ponse was received\.$/; 69 /^The message port closed before a res?ponse was received\.$/;
62 70
63 // This is the error Firefox throws when a message listener is not a 71 // This is the error Firefox throws when a message listener is not a
64 // function. 72 // function.
65 const invalidMessageListenerError = "Invalid listener for runtime.onMessage."; 73 const invalidMessageListenerError = "Invalid listener for runtime.onMessage.";
66 74
67 let messageListeners = new WeakMap(); 75 let messageListeners = new WeakMap();
68 76
69 function wrapAsyncAPI(api) 77 function getAPIWrappables(api)
70 { 78 {
71 let object = browser; 79 let object = browser;
72 let path = api.split("."); 80 let path = api.split(".");
73 let name = path.pop(); 81 let name = path.pop();
74 82
75 for (let node of path) 83 for (let node of path)
76 { 84 {
77 object = object[node]; 85 object = object[node];
78 86
79 if (!object) 87 if (!object)
80 return; 88 return;
81 } 89 }
82 90
83 let func = object[name]; 91 let func = object[name];
84 if (!func) 92 if (!func)
85 return; 93 return;
94
95 return {object, name, func};
96 }
97
98 function wrapAsyncAPI(api)
99 {
100 let wrappables = getAPIWrappables(api);
101
102 if (!wrappables)
103 return;
104
105 let {object, name, func} = wrappables;
86 106
87 // If the property is not writable assigning it will fail, so we use 107 // If the property is not writable assigning it will fail, so we use
88 // Object.defineProperty here instead. Assuming the property isn't 108 // Object.defineProperty here instead. Assuming the property isn't
89 // inherited its other attributes (e.g. enumerable) are preserved, 109 // inherited its other attributes (e.g. enumerable) are preserved,
90 // except for accessor attributes (e.g. get and set) which are discarded 110 // except for accessor attributes (e.g. get and set) which are discarded
91 // since we're specifying a value. 111 // since we're specifying a value.
92 Object.defineProperty(object, name, { 112 Object.defineProperty(object, name, {
93 value(...args) 113 value(...args)
94 { 114 {
95 let lastArgumentType = typeof args[args.length - 1];
96
97 // If the last argument is undefined, we assume it stands for the
98 // optional callback.
99 if (lastArgumentType == "function" || lastArgumentType == "undefined")
100 throw new Error("Callbacks are no longer supported.");
101
102 let resolvePromise = null; 115 let resolvePromise = null;
103 let rejectPromise = null; 116 let rejectPromise = null;
104 117
105 func.call(object, ...args, result => 118 func.call(object, ...args, result =>
106 { 119 {
107 let error = browser.runtime.lastError; 120 let error = browser.runtime.lastError;
108 if (error && !portClosedBeforeResponseError.test(error.message)) 121 if (error && !portClosedBeforeResponseError.test(error.message))
109 { 122 {
110 // runtime.lastError is already an Error instance on Edge, while on 123 // runtime.lastError is already an Error instance on Edge, while on
111 // Chrome it is a plain object with only a message property. 124 // Chrome it is a plain object with only a message property.
112 if (!(error instanceof Error)) 125 if (!(error instanceof Error))
113 error = new Error(error.message); 126 error = new Error(error.message);
114 127
115 rejectPromise(error); 128 rejectPromise(error);
116 } 129 }
117 else 130 else
118 { 131 {
119 resolvePromise(result); 132 resolvePromise(result);
120 } 133 }
121 }); 134 });
122 135
123 return new Promise((resolve, reject) => 136 return new Promise((resolve, reject) =>
124 { 137 {
125 resolvePromise = resolve; 138 resolvePromise = resolve;
126 rejectPromise = reject; 139 rejectPromise = reject;
127 }); 140 });
141 }
142 });
143 }
144
145 function wrapSyncAPI(api)
146 {
147 let wrappables = getAPIWrappables(api);
148
149 if (!wrappables)
150 return;
151
152 let {object, name, func} = wrappables;
153
154 Object.defineProperty(object, name, {
155 value(...args)
156 {
157 return Promise.resolve(func.call(object, ...args));
128 } 158 }
129 }); 159 });
130 } 160 }
131 161
132 function wrapRuntimeOnMessage() 162 function wrapRuntimeOnMessage()
133 { 163 {
134 let {onMessage} = browser.runtime; 164 let {onMessage} = browser.runtime;
135 let {addListener, removeListener} = onMessage; 165 let {addListener, removeListener} = onMessage;
136 166
137 onMessage.addListener = function(listener) 167 onMessage.addListener = function(listener)
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
200 { 230 {
201 return !(browser.storage.local.get([]) instanceof Promise); 231 return !(browser.storage.local.get([]) instanceof Promise);
202 } 232 }
203 catch (error) 233 catch (error)
204 { 234 {
205 } 235 }
206 236
207 return true; 237 return true;
208 } 238 }
209 239
240 function acceptsCallback(func, args)
241 {
242 try
243 {
244 func(...args, () => {});
245 return true;
246 }
247 catch (e)
248 {
249 return false;
250 }
251 }
252
210 if (shouldWrapAPIs()) 253 if (shouldWrapAPIs())
211 { 254 {
212 // Unlike Firefox and Microsoft Edge, Chrome doesn't have a "browser" 255 // Unlike Firefox and Microsoft Edge, Chrome doesn't have a "browser"
213 // object, but provides the extension API through the "chrome" namespace 256 // object, but provides the extension API through the "chrome" namespace
214 // (non-standard). 257 // (non-standard).
215 if (typeof browser == "undefined") 258 if (typeof browser == "undefined")
216 window.browser = chrome; 259 self.browser = chrome;
260
261 for (let [api, ...testArgs] of maybeAsyncAPIs)
262 {
263 let wrappables = getAPIWrappables(api);
264
265 if (!wrappables)
266 continue;
267
268 let {func} = wrappables;
269
270 (acceptsCallback(func, testArgs) ? asyncAPIs : syncAPIs).push(api);
271 }
217 272
218 for (let api of asyncAPIs) 273 for (let api of asyncAPIs)
219 wrapAsyncAPI(api); 274 wrapAsyncAPI(api);
275
276 for (let api of syncAPIs)
277 wrapSyncAPI(api);
220 278
221 wrapRuntimeOnMessage(); 279 wrapRuntimeOnMessage();
222 } 280 }
223 281
224 // Workaround since HTMLCollection, NodeList, StyleSheetList, and CSSRuleList 282 // Workaround since HTMLCollection, NodeList, StyleSheetList, and CSSRuleList
225 // didn't have iterator support before Chrome 51. 283 // didn't have iterator support before Chrome 51.
226 // https://bugs.chromium.org/p/chromium/issues/detail?id=401699 284 // https://bugs.chromium.org/p/chromium/issues/detail?id=401699
227 for (let object of [HTMLCollection, NodeList, StyleSheetList, CSSRuleList]) 285 for (let object of [HTMLCollection, NodeList, StyleSheetList, CSSRuleList])
228 { 286 {
229 if (!(Symbol.iterator in object.prototype)) 287 if (!(Symbol.iterator in object.prototype))
230 object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; 288 object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
231 } 289 }
232 } 290 }
291
292 // Object.values is not supported in Chrome <54.
293 if (!("values" in Object))
294 Object.values = obj => Object.keys(obj).map(key => obj[key]);
295
296 // Microsoft Edge (42.17134.1.0) doesn't support webRequest.ResourceType, but
297 // we can obtain the list of accepted resource types from the error message
298 // when creating an onBeforeRequest event listener with an unsupported resource
299 // type.
300 if ("webRequest" in browser && !("ResourceType" in browser.webRequest))
301 {
302 try
303 {
304 browser.webRequest.onBeforeRequest.addListener(
305 details => {}, {urls: ["<all_urls>"], types: ["foo"]}
306 );
307 }
308 catch (error)
309 {
310 let errorMessage = error.toString();
311 errorMessage = errorMessage.substr(errorMessage.lastIndexOf(":"));
312
313 browser.webRequest.ResourceType = {};
314 if (errorMessage.includes("main_frame"))
315 {
316 for (let type of errorMessage.match(/[a-z_]+/g))
317 browser.webRequest.ResourceType[type.toUpperCase()] = type;
318 }
319 }
320 }
321
322 // Chrome <50 does not support createImageBitmap, this is a simplistic
323 // polyfill which only fulfills the usecase of accepting a Blob containing
324 // an image and returning something which CanvasRenderingContext2D.drawImage()
325 // accepts.
326 if (typeof createImageBitmap == "undefined")
327 {
328 self.createImageBitmap = blob =>
329 {
330 return new Promise((resolve, reject) =>
331 {
332 let image = new Image();
333 image.src = URL.createObjectURL(blob);
334 image.addEventListener("load", () =>
335 {
336 URL.revokeObjectURL(image.src);
337 resolve(image);
338 });
339 image.addEventListener("error", () =>
340 {
341 URL.revokeObjectURL(image.src);
342 reject("createImageBitmap failed");
343 });
344 });
345 };
346 }
347
348 // Chrome <69 does not support OffscreenCanvas
349 if (typeof OffscreenCanvas == "undefined")
350 {
351 self.OffscreenCanvas = function(width, height)
352 {
353 let canvas = document.createElement("canvas");
354 canvas.width = width;
355 canvas.height = height;
356 return canvas;
357 };
358 }
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld