| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 /* | 
|  | 2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|  | 3  * Copyright (C) 2006-2017 eyeo GmbH | 
|  | 4  * | 
|  | 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 | 
|  | 7  * published by the Free Software Foundation. | 
|  | 8  * | 
|  | 9  * Adblock Plus is distributed in the hope that it will be useful, | 
|  | 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 12  * GNU General Public License for more details. | 
|  | 13  * | 
|  | 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/>. | 
|  | 16  */ | 
|  | 17 | 
|  | 18 /* eslint-env node */ | 
|  | 19 /* eslint no-console: "off" */ | 
|  | 20 | 
|  | 21 "use strict"; | 
|  | 22 | 
|  | 23 const childProcess = require("child_process"); | 
|  | 24 const fs = require("fs"); | 
|  | 25 const https = require("https"); | 
|  | 26 const os = require("os"); | 
|  | 27 const path = require("path"); | 
|  | 28 | 
|  | 29 const remoteInterface = require("chrome-remote-interface"); | 
|  | 30 const unzip = require("unzip"); | 
|  | 31 | 
|  | 32 const CHROMIUM_REVISION = 467222; | 
|  | 33 | 
|  | 34 function rmdir(dirPath) | 
|  | 35 { | 
|  | 36   for (let file of fs.readdirSync(dirPath)) | 
|  | 37   { | 
|  | 38     let filePath = path.join(dirPath, file); | 
|  | 39     try | 
|  | 40     { | 
|  | 41       if (fs.statSync(filePath).isDirectory()) | 
|  | 42         rmdir(filePath); | 
|  | 43       else | 
|  | 44         fs.unlinkSync(filePath); | 
|  | 45     } | 
|  | 46     catch (error) | 
|  | 47     { | 
|  | 48       console.error(error); | 
|  | 49     } | 
|  | 50   } | 
|  | 51 | 
|  | 52   try | 
|  | 53   { | 
|  | 54     fs.rmdirSync(dirPath); | 
|  | 55   } | 
|  | 56   catch (error) | 
|  | 57   { | 
|  | 58     console.error(error); | 
|  | 59   } | 
|  | 60 } | 
|  | 61 | 
|  | 62 function getChromiumExecutable(chromiumDir) | 
|  | 63 { | 
|  | 64   switch (process.platform) | 
|  | 65   { | 
|  | 66     case "win32": | 
|  | 67       return path.join(chromiumDir, "chrome-win32", "chrome.exe"); | 
|  | 68     case "linux": | 
|  | 69       return path.join(chromiumDir, "chrome-linux", "chrome"); | 
|  | 70     case "darwin": | 
|  | 71       return path.join(chromiumDir, "chrome-mac", "Chromium.app", "Contents", | 
|  | 72                        "MacOS", "Chromium"); | 
|  | 73     default: | 
|  | 74       throw new Error("Unexpected platform"); | 
|  | 75   } | 
|  | 76 } | 
|  | 77 | 
|  | 78 function ensureChromium() | 
|  | 79 { | 
|  | 80   let {platform} = process; | 
|  | 81   if (platform == "win32") | 
|  | 82     platform += "-" + process.arch; | 
|  | 83   let buildTypes = { | 
|  | 84     "win32-ia32": ["Win", "chrome-win32.zip"], | 
|  | 85     "win32-x64": ["Win_x64", "chrome-win32.zip"], | 
|  | 86     "linux": ["Linux_x64", "chrome-linux.zip"], | 
|  | 87     "darwin": ["Mac", "chrome-mac.zip"] | 
|  | 88   }; | 
|  | 89 | 
|  | 90   if (!buildTypes.hasOwnProperty(platform)) | 
|  | 91   { | 
|  | 92     let err = new Error(`Cannot run browser tests, ${platform} is unsupported`); | 
|  | 93     return Promise.reject(err); | 
|  | 94   } | 
|  | 95 | 
|  | 96   let chromiumDir = path.join(__dirname, "chromium-snapshots", | 
|  | 97                               `chromium-${platform}-${CHROMIUM_REVISION}`); | 
|  | 98   if (fs.existsSync(chromiumDir)) | 
|  | 99     return Promise.resolve(getChromiumExecutable(chromiumDir)); | 
|  | 100 | 
|  | 101   if (!fs.existsSync(path.dirname(chromiumDir))) | 
|  | 102     fs.mkdirSync(path.dirname(chromiumDir)); | 
|  | 103   return new Promise((resolve, reject) => | 
|  | 104   { | 
|  | 105     console.info("Downloading Chromium..."); | 
|  | 106     let [dir, fileName] = buildTypes[platform]; | 
|  | 107     let url = `https://www.googleapis.com/download/storage/v1/b/chromium-browser
     -snapshots/o/${dir}%2F${CHROMIUM_REVISION}%2F${fileName}?alt=media`; | 
|  | 108     https.get(url, response => | 
|  | 109     { | 
|  | 110       if (response.statusCode != 200) | 
|  | 111       { | 
|  | 112         reject(new Error(`Unexpected server response: ${response.statusCode}`)); | 
|  | 113         response.resume(); | 
|  | 114         return; | 
|  | 115       } | 
|  | 116 | 
|  | 117       response.pipe(unzip.Extract({path: chromiumDir})) | 
|  | 118               .on("error", reject) | 
|  | 119               .on("close", () => resolve(getChromiumExecutable(chromiumDir))); | 
|  | 120     }).on("error", reject); | 
|  | 121   }); | 
|  | 122 } | 
|  | 123 | 
|  | 124 function startChromium(chromiumPath) | 
|  | 125 { | 
|  | 126   fs.chmodSync(chromiumPath, fs.constants.S_IRWXU); | 
|  | 127 | 
|  | 128   let dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "chromium-data")); | 
|  | 129   let child = null; | 
|  | 130   return { | 
|  | 131     kill: () => child && child.kill(), | 
|  | 132     done: new Promise((resolve, reject) => | 
|  | 133     { | 
|  | 134       child = childProcess.execFile(chromiumPath, [ | 
|  | 135         "--headless", "--single-process", "--disable-gpu", "--no-sandbox", | 
|  | 136         "--allow-file-access-from-files", "--remote-debugging-port=9222", | 
|  | 137         "--user-data-dir=" + dataDir | 
|  | 138       ], error => | 
|  | 139       { | 
|  | 140         rmdir(dataDir); | 
|  | 141         if (error) | 
|  | 142           reject(error); | 
|  | 143         else | 
|  | 144           resolve(); | 
|  | 145       }); | 
|  | 146     }) | 
|  | 147   }; | 
|  | 148 } | 
|  | 149 | 
|  | 150 function throwException(details, url) | 
|  | 151 { | 
|  | 152   let text = details.exception ? details.exception.description : details.text; | 
|  | 153   if (!details.stackTrace) | 
|  | 154   { | 
|  | 155     // ExceptionDetails uses zero-based line and column numbers. | 
|  | 156     text += `\n    at ${details.url || url}:` + | 
|  | 157             (details.lineNumber + 1) + ":" + | 
|  | 158             (details.columnNumber + 1); | 
|  | 159   } | 
|  | 160   throw text; | 
|  | 161 } | 
|  | 162 | 
|  | 163 function reportMessage(text, level) | 
|  | 164 { | 
|  | 165   let method = { | 
|  | 166     log: "log", | 
|  | 167     warning: "warn", | 
|  | 168     error: "error", | 
|  | 169     debug: "log", | 
|  | 170     info: "info" | 
|  | 171   }[level] || "log"; | 
|  | 172   console[method](text); | 
|  | 173 } | 
|  | 174 | 
|  | 175 function connectRemoteInterface(attempt) | 
|  | 176 { | 
|  | 177   return remoteInterface().catch(error => | 
|  | 178   { | 
|  | 179     attempt = attempt || 1; | 
|  | 180     if (attempt > 50) | 
|  | 181     { | 
|  | 182       // Stop trying to connect after 10 seconds | 
|  | 183       throw error; | 
|  | 184     } | 
|  | 185 | 
|  | 186     return new Promise((resolve, reject) => | 
|  | 187     { | 
|  | 188       setTimeout(() => | 
|  | 189       { | 
|  | 190         connectRemoteInterface(attempt + 1).then(resolve).catch(reject); | 
|  | 191       }, 200); | 
|  | 192     }); | 
|  | 193   }); | 
|  | 194 } | 
|  | 195 | 
|  | 196 function runBootstrap(initialPage, bootstrapPath, bootstrapArgs) | 
|  | 197 { | 
|  | 198   return connectRemoteInterface().then(async client => | 
|  | 199   { | 
|  | 200     try | 
|  | 201     { | 
|  | 202       let {Runtime, Log, Console, Page} = client; | 
|  | 203 | 
|  | 204       await Log.enable(); | 
|  | 205       Log.entryAdded(({entry}) => | 
|  | 206       { | 
|  | 207         reportMessage(entry.text, entry.level); | 
|  | 208       }); | 
|  | 209 | 
|  | 210       await Console.enable(); | 
|  | 211       Console.messageAdded(({message}) => | 
|  | 212       { | 
|  | 213         reportMessage(message.text, message.level); | 
|  | 214       }); | 
|  | 215 | 
|  | 216       await Page.navigate({url: initialPage}); | 
|  | 217 | 
|  | 218       await Runtime.enable(); | 
|  | 219       let compileResult = await Runtime.compileScript({ | 
|  | 220         expression: fs.readFileSync(bootstrapPath, "utf-8"), | 
|  | 221         sourceURL: bootstrapPath, | 
|  | 222         persistScript: true | 
|  | 223       }); | 
|  | 224       if (compileResult.exceptionDetails) | 
|  | 225         throwException(compileResult.exceptionDetails, bootstrapPath); | 
|  | 226 | 
|  | 227       let runResult = await Runtime.runScript({ | 
|  | 228         scriptId: compileResult.scriptId | 
|  | 229       }); | 
|  | 230       if (runResult.exceptionDetails) | 
|  | 231         throwException(runResult.exceptionDetails, bootstrapPath); | 
|  | 232 | 
|  | 233       let callResult = await Runtime.callFunctionOn({ | 
|  | 234         objectId: runResult.result.objectId, | 
|  | 235         functionDeclaration: "function(...args) {return this(...args);}", | 
|  | 236         arguments: bootstrapArgs.map(url => ({value: url})) | 
|  | 237       }); | 
|  | 238       if (callResult.exceptionDetails) | 
|  | 239         throwException(callResult.exceptionDetails, bootstrapPath); | 
|  | 240 | 
|  | 241       let promiseResult = await Runtime.awaitPromise({ | 
|  | 242         promiseObjectId: callResult.result.objectId | 
|  | 243       }); | 
|  | 244       if (promiseResult.exceptionDetails) | 
|  | 245         throwException(promiseResult.exceptionDetails, bootstrapPath); | 
|  | 246     } | 
|  | 247     finally | 
|  | 248     { | 
|  | 249       client.close(); | 
|  | 250     } | 
|  | 251   }); | 
|  | 252 } | 
|  | 253 | 
|  | 254 module.exports = function(initialPage, bootstrapPath, bootstrapArgs) | 
|  | 255 { | 
|  | 256   return ensureChromium().then(chromiumPath => | 
|  | 257   { | 
|  | 258     let child = startChromium(chromiumPath); | 
|  | 259     return Promise.race([ | 
|  | 260       child.done, | 
|  | 261       runBootstrap(initialPage, bootstrapPath, bootstrapArgs) | 
|  | 262     ]).then(result => | 
|  | 263     { | 
|  | 264       child.kill(); | 
|  | 265       return result; | 
|  | 266     }).catch(error => | 
|  | 267     { | 
|  | 268       child.kill(); | 
|  | 269       throw error; | 
|  | 270     }); | 
|  | 271   }); | 
|  | 272 }; | 
| OLD | NEW | 
|---|