├── .babelrc ├── .gitignore ├── .prettierrc ├── README.md ├── dist-zip └── fakescreenshot-v1.0.0.zip ├── dist ├── background.js ├── icons │ ├── 1.png │ ├── 128.png │ ├── 2.png │ ├── 48.png │ ├── FakeScreenshot.png │ ├── ic_launcher_APP.png │ ├── icon_128.png │ ├── icon_48.png │ ├── screenshot128.png │ ├── screenshot48.png │ ├── 截图 (1).png │ └── 截图.png ├── jquery.js ├── jquery.upload.js ├── manifest.json ├── options │ ├── options.css │ ├── options.html │ ├── options.js │ ├── 图片.webm │ ├── 基础.webm │ ├── 弹框.webm │ └── 检测.webm ├── page.js └── popup │ ├── popup.css │ ├── popup.html │ └── popup.js ├── package.json ├── preview ├── basic.gif ├── check.gif ├── check.png ├── dialog.gif ├── picture.gif └── test.png ├── scripts └── build-zip.js ├── src ├── background.js ├── icons │ ├── 128.png │ ├── 48.png │ └── icon.xcf ├── jquery.js ├── jquery.upload.js ├── manifest.json ├── options │ ├── App.vue │ ├── options.html │ └── options.js ├── page.js └── popup │ ├── App.vue │ ├── popup.html │ ├── popup.js │ └── router │ ├── index.js │ ├── pages │ └── Index.vue │ └── routes.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-optional-chaining" 4 | ], 5 | "presets": [ 6 | ["@babel/preset-env", { 7 | "useBuiltIns": "usage", 8 | "corejs": 3, 9 | "targets": { 10 | // https://jamie.build/last-2-versions 11 | "browsers": ["> 0.25%", "not ie 11", "not op_mini all"] 12 | } 13 | }] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /*.log 3 | # /dist 4 | # /dist-zip 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 180, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FakeScreenshot2.0/虚假截图助手2.0 2 | > 这是一个可以**伪造任何网站界面截图**的工具。 3 | > 4 | > 但本工具的目的其实不是破坏,而是为了警告(不懂编程的)普通人:**不要轻易相信网上看到的“截图”!** 5 | 6 | 7 | 8 | **2020-4-26:突破性更新**,可以修改任何网站的任何文字/图片。 9 | 10 | ### 功能预览 11 | 12 | --- 13 | 14 | | ![basic](preview/basic.gif) | ![basic](preview/dialog.gif) | 15 | | ---- | ---- | 16 | | ![basic](preview/picture.gif) | ![basic](preview/check.gif) | 17 | 18 | 19 | ### 如何使用? 20 | 21 | --- 22 | 23 | 下载正式版: 24 | 25 | 1. 准备好Chrome浏览器和**翻墙**工具。 26 | 2. 从Chrome扩展商店[安装](https://chrome.google.com/webstore/detail/fakescreenshot/jiojdapfbpmhpihdejiglphhoeakjhmi)即可。 27 | 28 | 29 | 30 | 下载开发者版(方便不能翻墙的人使用): 31 | 32 | 1. 下载Chrome浏览器(版本越高越好) 33 | 2. 从[本仓库中](https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/master/dist-zip/fakescreenshot-v1.0.0.zip)下载开发版,然后解压缩。 34 | 3. 在浏览器打开`chrome://extension`页面,并且打开右上角的“开发者模式” 35 | 4. 点击左上角“加载已解压的扩展程序”并选择解压好的文件夹 36 | 37 | 38 | 39 | ### 项目灵感 40 | 41 | --- 42 | 43 | 如果有一天你在群里看到这么一张图,你第一反应是什么? 44 | 45 | ![微博截图](https://i.loli.net/2019/05/09/5cd4436bea0a1.jpg) 46 | 47 | “卧槽,这么快?” 48 | 49 | “正好这段时间没事做,学一下” 50 | 51 | “和2.0有什么区别啊?” 52 | 53 | “求你们别更了,我学不动了.jpg ?” 54 | 55 | "支持TS吗?" 56 | 57 | **不管怎样,只要第一反应不是怀疑其真实性,那么你就很可能成为“假截图”的受害者!** 58 | 59 | 60 | 61 | 我们都曾看到过各种截屏:包括不限于`知乎`、`微博`、`豆瓣`、`NGA`、`V2EX`、`QQ`、`微信` 、`各种新闻站`... 62 | 63 | 如果那些截屏内容只涉及到段子还好,但多数情况不是。它们往往和某事/某人有关,这(假截图)就**极有可能**导致人们对该事/人产生错误的看法(之所以说错误,是因为我认为**大多数**假截图的制作者都怀有不良动机。) 64 | 65 | 另外,**多数人并不会去主动验证该截图描述事情的真伪**(不这么做的原因这里不做讨论),这是“假截图”泛滥的重要原因之一。 66 | 67 | 68 | 69 | ### 对此我能做什么呢? 70 | 71 | --- 72 | 73 | **三个方向:** 74 | 75 | 1. 阻止传播(极难实现) 76 | 2. 告诉人们应该主动去求证(很难实现) 77 | 3. 告诉人们应该怀疑一切截图的真实性(比较容易) 78 | 79 | 我选择了第三个方向。 80 | 81 | 而在方法的选择上,我选择反其道而行之:开发一个可以非常简单地修改**任何网站文字/图片**的工具。(当然,只支持PC版) 82 | 83 | **我通过该工具本身来告诉人们这样一件事:“啊,原来任何网页的截图都可能是假的!”** 84 | 85 | 86 | 87 | ### 水印 88 | 89 | --- 90 | 91 | 本工具的目的是传递(如上的)信息,而不是破坏。因此所有经过本工具制作出来的截图都被打上了**水印**。 92 | 93 | 水印的处理分为两种: 94 | 95 | 1. 修改网页文字时会打上透明水印。(肉眼不可见,但经过专门提供的[水印检查]()工具可以检查出来) 96 | 2. 修改图片时会打上肉眼可见(但比较浅)的水印。 97 | 98 | ![test](preview/test.png) 99 | 100 | 101 | 102 | ![test](preview/check.png) 103 | 104 | -------------------------------------------------------------------------------- /dist-zip/fakescreenshot-v1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist-zip/fakescreenshot-v1.0.0.zip -------------------------------------------------------------------------------- /dist/background.js: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | /* -------------------------------------------------- */ 4 | 5 | /* Start of Webpack Hot Extension Middleware */ 6 | 7 | /* ================================================== */ 8 | 9 | /* This will be converted into a lodash templ., any */ 10 | 11 | /* external argument must be provided using it */ 12 | 13 | /* -------------------------------------------------- */ 14 | (function (window) { 15 | var injectionContext = { 16 | browser: null 17 | }; 18 | (function () { 19 | ""||(function (global, factory) { 20 | if (typeof define === "function" && define.amd) { 21 | define("webextension-polyfill", ["module"], factory); 22 | } else if (typeof exports !== "undefined") { 23 | factory(module); 24 | } else { 25 | var mod = { 26 | exports: {} 27 | }; 28 | factory(mod); 29 | global.browser = mod.exports; 30 | } 31 | })(this, function (module) { 32 | /* webextension-polyfill - v0.5.0 - Thu Sep 26 2019 22:22:26 */ 33 | /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ 34 | /* vim: set sts=2 sw=2 et tw=80: */ 35 | /* This Source Code Form is subject to the terms of the Mozilla Public 36 | * License, v. 2.0. If a copy of the MPL was not distributed with this 37 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 38 | "use strict"; 39 | 40 | if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) { 41 | const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received."; 42 | const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)"; 43 | 44 | // Wrapping the bulk of this polyfill in a one-time-use function is a minor 45 | // optimization for Firefox. Since Spidermonkey does not fully parse the 46 | // contents of a function until the first time it's called, and since it will 47 | // never actually need to be called, this allows the polyfill to be included 48 | // in Firefox nearly for free. 49 | const wrapAPIs = extensionAPIs => { 50 | // NOTE: apiMetadata is associated to the content of the api-metadata.json file 51 | // at build time by replacing the following "include" with the content of the 52 | // JSON file. 53 | const apiMetadata = { 54 | "alarms": { 55 | "clear": { 56 | "minArgs": 0, 57 | "maxArgs": 1 58 | }, 59 | "clearAll": { 60 | "minArgs": 0, 61 | "maxArgs": 0 62 | }, 63 | "get": { 64 | "minArgs": 0, 65 | "maxArgs": 1 66 | }, 67 | "getAll": { 68 | "minArgs": 0, 69 | "maxArgs": 0 70 | } 71 | }, 72 | "bookmarks": { 73 | "create": { 74 | "minArgs": 1, 75 | "maxArgs": 1 76 | }, 77 | "get": { 78 | "minArgs": 1, 79 | "maxArgs": 1 80 | }, 81 | "getChildren": { 82 | "minArgs": 1, 83 | "maxArgs": 1 84 | }, 85 | "getRecent": { 86 | "minArgs": 1, 87 | "maxArgs": 1 88 | }, 89 | "getSubTree": { 90 | "minArgs": 1, 91 | "maxArgs": 1 92 | }, 93 | "getTree": { 94 | "minArgs": 0, 95 | "maxArgs": 0 96 | }, 97 | "move": { 98 | "minArgs": 2, 99 | "maxArgs": 2 100 | }, 101 | "remove": { 102 | "minArgs": 1, 103 | "maxArgs": 1 104 | }, 105 | "removeTree": { 106 | "minArgs": 1, 107 | "maxArgs": 1 108 | }, 109 | "search": { 110 | "minArgs": 1, 111 | "maxArgs": 1 112 | }, 113 | "update": { 114 | "minArgs": 2, 115 | "maxArgs": 2 116 | } 117 | }, 118 | "browserAction": { 119 | "disable": { 120 | "minArgs": 0, 121 | "maxArgs": 1, 122 | "fallbackToNoCallback": true 123 | }, 124 | "enable": { 125 | "minArgs": 0, 126 | "maxArgs": 1, 127 | "fallbackToNoCallback": true 128 | }, 129 | "getBadgeBackgroundColor": { 130 | "minArgs": 1, 131 | "maxArgs": 1 132 | }, 133 | "getBadgeText": { 134 | "minArgs": 1, 135 | "maxArgs": 1 136 | }, 137 | "getPopup": { 138 | "minArgs": 1, 139 | "maxArgs": 1 140 | }, 141 | "getTitle": { 142 | "minArgs": 1, 143 | "maxArgs": 1 144 | }, 145 | "openPopup": { 146 | "minArgs": 0, 147 | "maxArgs": 0 148 | }, 149 | "setBadgeBackgroundColor": { 150 | "minArgs": 1, 151 | "maxArgs": 1, 152 | "fallbackToNoCallback": true 153 | }, 154 | "setBadgeText": { 155 | "minArgs": 1, 156 | "maxArgs": 1, 157 | "fallbackToNoCallback": true 158 | }, 159 | "setIcon": { 160 | "minArgs": 1, 161 | "maxArgs": 1 162 | }, 163 | "setPopup": { 164 | "minArgs": 1, 165 | "maxArgs": 1, 166 | "fallbackToNoCallback": true 167 | }, 168 | "setTitle": { 169 | "minArgs": 1, 170 | "maxArgs": 1, 171 | "fallbackToNoCallback": true 172 | } 173 | }, 174 | "browsingData": { 175 | "remove": { 176 | "minArgs": 2, 177 | "maxArgs": 2 178 | }, 179 | "removeCache": { 180 | "minArgs": 1, 181 | "maxArgs": 1 182 | }, 183 | "removeCookies": { 184 | "minArgs": 1, 185 | "maxArgs": 1 186 | }, 187 | "removeDownloads": { 188 | "minArgs": 1, 189 | "maxArgs": 1 190 | }, 191 | "removeFormData": { 192 | "minArgs": 1, 193 | "maxArgs": 1 194 | }, 195 | "removeHistory": { 196 | "minArgs": 1, 197 | "maxArgs": 1 198 | }, 199 | "removeLocalStorage": { 200 | "minArgs": 1, 201 | "maxArgs": 1 202 | }, 203 | "removePasswords": { 204 | "minArgs": 1, 205 | "maxArgs": 1 206 | }, 207 | "removePluginData": { 208 | "minArgs": 1, 209 | "maxArgs": 1 210 | }, 211 | "settings": { 212 | "minArgs": 0, 213 | "maxArgs": 0 214 | } 215 | }, 216 | "commands": { 217 | "getAll": { 218 | "minArgs": 0, 219 | "maxArgs": 0 220 | } 221 | }, 222 | "contextMenus": { 223 | "remove": { 224 | "minArgs": 1, 225 | "maxArgs": 1 226 | }, 227 | "removeAll": { 228 | "minArgs": 0, 229 | "maxArgs": 0 230 | }, 231 | "update": { 232 | "minArgs": 2, 233 | "maxArgs": 2 234 | } 235 | }, 236 | "cookies": { 237 | "get": { 238 | "minArgs": 1, 239 | "maxArgs": 1 240 | }, 241 | "getAll": { 242 | "minArgs": 1, 243 | "maxArgs": 1 244 | }, 245 | "getAllCookieStores": { 246 | "minArgs": 0, 247 | "maxArgs": 0 248 | }, 249 | "remove": { 250 | "minArgs": 1, 251 | "maxArgs": 1 252 | }, 253 | "set": { 254 | "minArgs": 1, 255 | "maxArgs": 1 256 | } 257 | }, 258 | "devtools": { 259 | "inspectedWindow": { 260 | "eval": { 261 | "minArgs": 1, 262 | "maxArgs": 2, 263 | "singleCallbackArg": false 264 | } 265 | }, 266 | "panels": { 267 | "create": { 268 | "minArgs": 3, 269 | "maxArgs": 3, 270 | "singleCallbackArg": true 271 | } 272 | } 273 | }, 274 | "downloads": { 275 | "cancel": { 276 | "minArgs": 1, 277 | "maxArgs": 1 278 | }, 279 | "download": { 280 | "minArgs": 1, 281 | "maxArgs": 1 282 | }, 283 | "erase": { 284 | "minArgs": 1, 285 | "maxArgs": 1 286 | }, 287 | "getFileIcon": { 288 | "minArgs": 1, 289 | "maxArgs": 2 290 | }, 291 | "open": { 292 | "minArgs": 1, 293 | "maxArgs": 1, 294 | "fallbackToNoCallback": true 295 | }, 296 | "pause": { 297 | "minArgs": 1, 298 | "maxArgs": 1 299 | }, 300 | "removeFile": { 301 | "minArgs": 1, 302 | "maxArgs": 1 303 | }, 304 | "resume": { 305 | "minArgs": 1, 306 | "maxArgs": 1 307 | }, 308 | "search": { 309 | "minArgs": 1, 310 | "maxArgs": 1 311 | }, 312 | "show": { 313 | "minArgs": 1, 314 | "maxArgs": 1, 315 | "fallbackToNoCallback": true 316 | } 317 | }, 318 | "extension": { 319 | "isAllowedFileSchemeAccess": { 320 | "minArgs": 0, 321 | "maxArgs": 0 322 | }, 323 | "isAllowedIncognitoAccess": { 324 | "minArgs": 0, 325 | "maxArgs": 0 326 | } 327 | }, 328 | "history": { 329 | "addUrl": { 330 | "minArgs": 1, 331 | "maxArgs": 1 332 | }, 333 | "deleteAll": { 334 | "minArgs": 0, 335 | "maxArgs": 0 336 | }, 337 | "deleteRange": { 338 | "minArgs": 1, 339 | "maxArgs": 1 340 | }, 341 | "deleteUrl": { 342 | "minArgs": 1, 343 | "maxArgs": 1 344 | }, 345 | "getVisits": { 346 | "minArgs": 1, 347 | "maxArgs": 1 348 | }, 349 | "search": { 350 | "minArgs": 1, 351 | "maxArgs": 1 352 | } 353 | }, 354 | "i18n": { 355 | "detectLanguage": { 356 | "minArgs": 1, 357 | "maxArgs": 1 358 | }, 359 | "getAcceptLanguages": { 360 | "minArgs": 0, 361 | "maxArgs": 0 362 | } 363 | }, 364 | "identity": { 365 | "launchWebAuthFlow": { 366 | "minArgs": 1, 367 | "maxArgs": 1 368 | } 369 | }, 370 | "idle": { 371 | "queryState": { 372 | "minArgs": 1, 373 | "maxArgs": 1 374 | } 375 | }, 376 | "management": { 377 | "get": { 378 | "minArgs": 1, 379 | "maxArgs": 1 380 | }, 381 | "getAll": { 382 | "minArgs": 0, 383 | "maxArgs": 0 384 | }, 385 | "getSelf": { 386 | "minArgs": 0, 387 | "maxArgs": 0 388 | }, 389 | "setEnabled": { 390 | "minArgs": 2, 391 | "maxArgs": 2 392 | }, 393 | "uninstallSelf": { 394 | "minArgs": 0, 395 | "maxArgs": 1 396 | } 397 | }, 398 | "notifications": { 399 | "clear": { 400 | "minArgs": 1, 401 | "maxArgs": 1 402 | }, 403 | "create": { 404 | "minArgs": 1, 405 | "maxArgs": 2 406 | }, 407 | "getAll": { 408 | "minArgs": 0, 409 | "maxArgs": 0 410 | }, 411 | "getPermissionLevel": { 412 | "minArgs": 0, 413 | "maxArgs": 0 414 | }, 415 | "update": { 416 | "minArgs": 2, 417 | "maxArgs": 2 418 | } 419 | }, 420 | "pageAction": { 421 | "getPopup": { 422 | "minArgs": 1, 423 | "maxArgs": 1 424 | }, 425 | "getTitle": { 426 | "minArgs": 1, 427 | "maxArgs": 1 428 | }, 429 | "hide": { 430 | "minArgs": 1, 431 | "maxArgs": 1, 432 | "fallbackToNoCallback": true 433 | }, 434 | "setIcon": { 435 | "minArgs": 1, 436 | "maxArgs": 1 437 | }, 438 | "setPopup": { 439 | "minArgs": 1, 440 | "maxArgs": 1, 441 | "fallbackToNoCallback": true 442 | }, 443 | "setTitle": { 444 | "minArgs": 1, 445 | "maxArgs": 1, 446 | "fallbackToNoCallback": true 447 | }, 448 | "show": { 449 | "minArgs": 1, 450 | "maxArgs": 1, 451 | "fallbackToNoCallback": true 452 | } 453 | }, 454 | "permissions": { 455 | "contains": { 456 | "minArgs": 1, 457 | "maxArgs": 1 458 | }, 459 | "getAll": { 460 | "minArgs": 0, 461 | "maxArgs": 0 462 | }, 463 | "remove": { 464 | "minArgs": 1, 465 | "maxArgs": 1 466 | }, 467 | "request": { 468 | "minArgs": 1, 469 | "maxArgs": 1 470 | } 471 | }, 472 | "runtime": { 473 | "getBackgroundPage": { 474 | "minArgs": 0, 475 | "maxArgs": 0 476 | }, 477 | "getPlatformInfo": { 478 | "minArgs": 0, 479 | "maxArgs": 0 480 | }, 481 | "openOptionsPage": { 482 | "minArgs": 0, 483 | "maxArgs": 0 484 | }, 485 | "requestUpdateCheck": { 486 | "minArgs": 0, 487 | "maxArgs": 0 488 | }, 489 | "sendMessage": { 490 | "minArgs": 1, 491 | "maxArgs": 3 492 | }, 493 | "sendNativeMessage": { 494 | "minArgs": 2, 495 | "maxArgs": 2 496 | }, 497 | "setUninstallURL": { 498 | "minArgs": 1, 499 | "maxArgs": 1 500 | } 501 | }, 502 | "sessions": { 503 | "getDevices": { 504 | "minArgs": 0, 505 | "maxArgs": 1 506 | }, 507 | "getRecentlyClosed": { 508 | "minArgs": 0, 509 | "maxArgs": 1 510 | }, 511 | "restore": { 512 | "minArgs": 0, 513 | "maxArgs": 1 514 | } 515 | }, 516 | "storage": { 517 | "local": { 518 | "clear": { 519 | "minArgs": 0, 520 | "maxArgs": 0 521 | }, 522 | "get": { 523 | "minArgs": 0, 524 | "maxArgs": 1 525 | }, 526 | "getBytesInUse": { 527 | "minArgs": 0, 528 | "maxArgs": 1 529 | }, 530 | "remove": { 531 | "minArgs": 1, 532 | "maxArgs": 1 533 | }, 534 | "set": { 535 | "minArgs": 1, 536 | "maxArgs": 1 537 | } 538 | }, 539 | "managed": { 540 | "get": { 541 | "minArgs": 0, 542 | "maxArgs": 1 543 | }, 544 | "getBytesInUse": { 545 | "minArgs": 0, 546 | "maxArgs": 1 547 | } 548 | }, 549 | "sync": { 550 | "clear": { 551 | "minArgs": 0, 552 | "maxArgs": 0 553 | }, 554 | "get": { 555 | "minArgs": 0, 556 | "maxArgs": 1 557 | }, 558 | "getBytesInUse": { 559 | "minArgs": 0, 560 | "maxArgs": 1 561 | }, 562 | "remove": { 563 | "minArgs": 1, 564 | "maxArgs": 1 565 | }, 566 | "set": { 567 | "minArgs": 1, 568 | "maxArgs": 1 569 | } 570 | } 571 | }, 572 | "tabs": { 573 | "captureVisibleTab": { 574 | "minArgs": 0, 575 | "maxArgs": 2 576 | }, 577 | "create": { 578 | "minArgs": 1, 579 | "maxArgs": 1 580 | }, 581 | "detectLanguage": { 582 | "minArgs": 0, 583 | "maxArgs": 1 584 | }, 585 | "discard": { 586 | "minArgs": 0, 587 | "maxArgs": 1 588 | }, 589 | "duplicate": { 590 | "minArgs": 1, 591 | "maxArgs": 1 592 | }, 593 | "executeScript": { 594 | "minArgs": 1, 595 | "maxArgs": 2 596 | }, 597 | "get": { 598 | "minArgs": 1, 599 | "maxArgs": 1 600 | }, 601 | "getCurrent": { 602 | "minArgs": 0, 603 | "maxArgs": 0 604 | }, 605 | "getZoom": { 606 | "minArgs": 0, 607 | "maxArgs": 1 608 | }, 609 | "getZoomSettings": { 610 | "minArgs": 0, 611 | "maxArgs": 1 612 | }, 613 | "highlight": { 614 | "minArgs": 1, 615 | "maxArgs": 1 616 | }, 617 | "insertCSS": { 618 | "minArgs": 1, 619 | "maxArgs": 2 620 | }, 621 | "move": { 622 | "minArgs": 2, 623 | "maxArgs": 2 624 | }, 625 | "query": { 626 | "minArgs": 1, 627 | "maxArgs": 1 628 | }, 629 | "reload": { 630 | "minArgs": 0, 631 | "maxArgs": 2 632 | }, 633 | "remove": { 634 | "minArgs": 1, 635 | "maxArgs": 1 636 | }, 637 | "removeCSS": { 638 | "minArgs": 1, 639 | "maxArgs": 2 640 | }, 641 | "sendMessage": { 642 | "minArgs": 2, 643 | "maxArgs": 3 644 | }, 645 | "setZoom": { 646 | "minArgs": 1, 647 | "maxArgs": 2 648 | }, 649 | "setZoomSettings": { 650 | "minArgs": 1, 651 | "maxArgs": 2 652 | }, 653 | "update": { 654 | "minArgs": 1, 655 | "maxArgs": 2 656 | } 657 | }, 658 | "topSites": { 659 | "get": { 660 | "minArgs": 0, 661 | "maxArgs": 0 662 | } 663 | }, 664 | "webNavigation": { 665 | "getAllFrames": { 666 | "minArgs": 1, 667 | "maxArgs": 1 668 | }, 669 | "getFrame": { 670 | "minArgs": 1, 671 | "maxArgs": 1 672 | } 673 | }, 674 | "webRequest": { 675 | "handlerBehaviorChanged": { 676 | "minArgs": 0, 677 | "maxArgs": 0 678 | } 679 | }, 680 | "windows": { 681 | "create": { 682 | "minArgs": 0, 683 | "maxArgs": 1 684 | }, 685 | "get": { 686 | "minArgs": 1, 687 | "maxArgs": 2 688 | }, 689 | "getAll": { 690 | "minArgs": 0, 691 | "maxArgs": 1 692 | }, 693 | "getCurrent": { 694 | "minArgs": 0, 695 | "maxArgs": 1 696 | }, 697 | "getLastFocused": { 698 | "minArgs": 0, 699 | "maxArgs": 1 700 | }, 701 | "remove": { 702 | "minArgs": 1, 703 | "maxArgs": 1 704 | }, 705 | "update": { 706 | "minArgs": 2, 707 | "maxArgs": 2 708 | } 709 | } 710 | }; 711 | 712 | if (Object.keys(apiMetadata).length === 0) { 713 | throw new Error("api-metadata.json has not been included in browser-polyfill"); 714 | } 715 | 716 | /** 717 | * A WeakMap subclass which creates and stores a value for any key which does 718 | * not exist when accessed, but behaves exactly as an ordinary WeakMap 719 | * otherwise. 720 | * 721 | * @param {function} createItem 722 | * A function which will be called in order to create the value for any 723 | * key which does not exist, the first time it is accessed. The 724 | * function receives, as its only argument, the key being created. 725 | */ 726 | class DefaultWeakMap extends WeakMap { 727 | constructor(createItem, items = undefined) { 728 | super(items); 729 | this.createItem = createItem; 730 | } 731 | 732 | get(key) { 733 | if (!this.has(key)) { 734 | this.set(key, this.createItem(key)); 735 | } 736 | 737 | return super.get(key); 738 | } 739 | } 740 | 741 | /** 742 | * Returns true if the given object is an object with a `then` method, and can 743 | * therefore be assumed to behave as a Promise. 744 | * 745 | * @param {*} value The value to test. 746 | * @returns {boolean} True if the value is thenable. 747 | */ 748 | const isThenable = value => { 749 | return value && typeof value === "object" && typeof value.then === "function"; 750 | }; 751 | 752 | /** 753 | * Creates and returns a function which, when called, will resolve or reject 754 | * the given promise based on how it is called: 755 | * 756 | * - If, when called, `chrome.runtime.lastError` contains a non-null object, 757 | * the promise is rejected with that value. 758 | * - If the function is called with exactly one argument, the promise is 759 | * resolved to that value. 760 | * - Otherwise, the promise is resolved to an array containing all of the 761 | * function's arguments. 762 | * 763 | * @param {object} promise 764 | * An object containing the resolution and rejection functions of a 765 | * promise. 766 | * @param {function} promise.resolve 767 | * The promise's resolution function. 768 | * @param {function} promise.rejection 769 | * The promise's rejection function. 770 | * @param {object} metadata 771 | * Metadata about the wrapped method which has created the callback. 772 | * @param {integer} metadata.maxResolvedArgs 773 | * The maximum number of arguments which may be passed to the 774 | * callback created by the wrapped async function. 775 | * 776 | * @returns {function} 777 | * The generated callback function. 778 | */ 779 | const makeCallback = (promise, metadata) => { 780 | return (...callbackArgs) => { 781 | if (extensionAPIs.runtime.lastError) { 782 | promise.reject(extensionAPIs.runtime.lastError); 783 | } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) { 784 | promise.resolve(callbackArgs[0]); 785 | } else { 786 | promise.resolve(callbackArgs); 787 | } 788 | }; 789 | }; 790 | 791 | const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments"; 792 | 793 | /** 794 | * Creates a wrapper function for a method with the given name and metadata. 795 | * 796 | * @param {string} name 797 | * The name of the method which is being wrapped. 798 | * @param {object} metadata 799 | * Metadata about the method being wrapped. 800 | * @param {integer} metadata.minArgs 801 | * The minimum number of arguments which must be passed to the 802 | * function. If called with fewer than this number of arguments, the 803 | * wrapper will raise an exception. 804 | * @param {integer} metadata.maxArgs 805 | * The maximum number of arguments which may be passed to the 806 | * function. If called with more than this number of arguments, the 807 | * wrapper will raise an exception. 808 | * @param {integer} metadata.maxResolvedArgs 809 | * The maximum number of arguments which may be passed to the 810 | * callback created by the wrapped async function. 811 | * 812 | * @returns {function(object, ...*)} 813 | * The generated wrapper function. 814 | */ 815 | const wrapAsyncFunction = (name, metadata) => { 816 | return function asyncFunctionWrapper(target, ...args) { 817 | if (args.length < metadata.minArgs) { 818 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); 819 | } 820 | 821 | if (args.length > metadata.maxArgs) { 822 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); 823 | } 824 | 825 | return new Promise((resolve, reject) => { 826 | if (metadata.fallbackToNoCallback) { 827 | // This API method has currently no callback on Chrome, but it return a promise on Firefox, 828 | // and so the polyfill will try to call it with a callback first, and it will fallback 829 | // to not passing the callback if the first call fails. 830 | try { 831 | target[name](...args, makeCallback({ resolve, reject }, metadata)); 832 | } catch (cbError) { 833 | console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError); 834 | 835 | target[name](...args); 836 | 837 | // Update the API method metadata, so that the next API calls will not try to 838 | // use the unsupported callback anymore. 839 | metadata.fallbackToNoCallback = false; 840 | metadata.noCallback = true; 841 | 842 | resolve(); 843 | } 844 | } else if (metadata.noCallback) { 845 | target[name](...args); 846 | resolve(); 847 | } else { 848 | target[name](...args, makeCallback({ resolve, reject }, metadata)); 849 | } 850 | }); 851 | }; 852 | }; 853 | 854 | /** 855 | * Wraps an existing method of the target object, so that calls to it are 856 | * intercepted by the given wrapper function. The wrapper function receives, 857 | * as its first argument, the original `target` object, followed by each of 858 | * the arguments passed to the original method. 859 | * 860 | * @param {object} target 861 | * The original target object that the wrapped method belongs to. 862 | * @param {function} method 863 | * The method being wrapped. This is used as the target of the Proxy 864 | * object which is created to wrap the method. 865 | * @param {function} wrapper 866 | * The wrapper function which is called in place of a direct invocation 867 | * of the wrapped method. 868 | * 869 | * @returns {Proxy} 870 | * A Proxy object for the given method, which invokes the given wrapper 871 | * method in its place. 872 | */ 873 | const wrapMethod = (target, method, wrapper) => { 874 | return new Proxy(method, { 875 | apply(targetMethod, thisObj, args) { 876 | return wrapper.call(thisObj, target, ...args); 877 | } 878 | }); 879 | }; 880 | 881 | let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); 882 | 883 | /** 884 | * Wraps an object in a Proxy which intercepts and wraps certain methods 885 | * based on the given `wrappers` and `metadata` objects. 886 | * 887 | * @param {object} target 888 | * The target object to wrap. 889 | * 890 | * @param {object} [wrappers = {}] 891 | * An object tree containing wrapper functions for special cases. Any 892 | * function present in this object tree is called in place of the 893 | * method in the same location in the `target` object tree. These 894 | * wrapper methods are invoked as described in {@see wrapMethod}. 895 | * 896 | * @param {object} [metadata = {}] 897 | * An object tree containing metadata used to automatically generate 898 | * Promise-based wrapper functions for asynchronous. Any function in 899 | * the `target` object tree which has a corresponding metadata object 900 | * in the same location in the `metadata` tree is replaced with an 901 | * automatically-generated wrapper function, as described in 902 | * {@see wrapAsyncFunction} 903 | * 904 | * @returns {Proxy} 905 | */ 906 | const wrapObject = (target, wrappers = {}, metadata = {}) => { 907 | let cache = Object.create(null); 908 | let handlers = { 909 | has(proxyTarget, prop) { 910 | return prop in target || prop in cache; 911 | }, 912 | 913 | get(proxyTarget, prop, receiver) { 914 | if (prop in cache) { 915 | return cache[prop]; 916 | } 917 | 918 | if (!(prop in target)) { 919 | return undefined; 920 | } 921 | 922 | let value = target[prop]; 923 | 924 | if (typeof value === "function") { 925 | // This is a method on the underlying object. Check if we need to do 926 | // any wrapping. 927 | 928 | if (typeof wrappers[prop] === "function") { 929 | // We have a special-case wrapper for this method. 930 | value = wrapMethod(target, target[prop], wrappers[prop]); 931 | } else if (hasOwnProperty(metadata, prop)) { 932 | // This is an async method that we have metadata for. Create a 933 | // Promise wrapper for it. 934 | let wrapper = wrapAsyncFunction(prop, metadata[prop]); 935 | value = wrapMethod(target, target[prop], wrapper); 936 | } else { 937 | // This is a method that we don't know or care about. Return the 938 | // original method, bound to the underlying object. 939 | value = value.bind(target); 940 | } 941 | } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) { 942 | // This is an object that we need to do some wrapping for the children 943 | // of. Create a sub-object wrapper for it with the appropriate child 944 | // metadata. 945 | value = wrapObject(value, wrappers[prop], metadata[prop]); 946 | } else { 947 | // We don't need to do any wrapping for this property, 948 | // so just forward all access to the underlying object. 949 | Object.defineProperty(cache, prop, { 950 | configurable: true, 951 | enumerable: true, 952 | get() { 953 | return target[prop]; 954 | }, 955 | set(value) { 956 | target[prop] = value; 957 | } 958 | }); 959 | 960 | return value; 961 | } 962 | 963 | cache[prop] = value; 964 | return value; 965 | }, 966 | 967 | set(proxyTarget, prop, value, receiver) { 968 | if (prop in cache) { 969 | cache[prop] = value; 970 | } else { 971 | target[prop] = value; 972 | } 973 | return true; 974 | }, 975 | 976 | defineProperty(proxyTarget, prop, desc) { 977 | return Reflect.defineProperty(cache, prop, desc); 978 | }, 979 | 980 | deleteProperty(proxyTarget, prop) { 981 | return Reflect.deleteProperty(cache, prop); 982 | } 983 | }; 984 | 985 | // Per contract of the Proxy API, the "get" proxy handler must return the 986 | // original value of the target if that value is declared read-only and 987 | // non-configurable. For this reason, we create an object with the 988 | // prototype set to `target` instead of using `target` directly. 989 | // Otherwise we cannot return a custom object for APIs that 990 | // are declared read-only and non-configurable, such as `chrome.devtools`. 991 | // 992 | // The proxy handlers themselves will still use the original `target` 993 | // instead of the `proxyTarget`, so that the methods and properties are 994 | // dereferenced via the original targets. 995 | let proxyTarget = Object.create(target); 996 | return new Proxy(proxyTarget, handlers); 997 | }; 998 | 999 | /** 1000 | * Creates a set of wrapper functions for an event object, which handles 1001 | * wrapping of listener functions that those messages are passed. 1002 | * 1003 | * A single wrapper is created for each listener function, and stored in a 1004 | * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener` 1005 | * retrieve the original wrapper, so that attempts to remove a 1006 | * previously-added listener work as expected. 1007 | * 1008 | * @param {DefaultWeakMap} wrapperMap 1009 | * A DefaultWeakMap object which will create the appropriate wrapper 1010 | * for a given listener function when one does not exist, and retrieve 1011 | * an existing one when it does. 1012 | * 1013 | * @returns {object} 1014 | */ 1015 | const wrapEvent = wrapperMap => ({ 1016 | addListener(target, listener, ...args) { 1017 | target.addListener(wrapperMap.get(listener), ...args); 1018 | }, 1019 | 1020 | hasListener(target, listener) { 1021 | return target.hasListener(wrapperMap.get(listener)); 1022 | }, 1023 | 1024 | removeListener(target, listener) { 1025 | target.removeListener(wrapperMap.get(listener)); 1026 | } 1027 | }); 1028 | 1029 | // Keep track if the deprecation warning has been logged at least once. 1030 | let loggedSendResponseDeprecationWarning = false; 1031 | 1032 | const onMessageWrappers = new DefaultWeakMap(listener => { 1033 | if (typeof listener !== "function") { 1034 | return listener; 1035 | } 1036 | 1037 | /** 1038 | * Wraps a message listener function so that it may send responses based on 1039 | * its return value, rather than by returning a sentinel value and calling a 1040 | * callback. If the listener function returns a Promise, the response is 1041 | * sent when the promise either resolves or rejects. 1042 | * 1043 | * @param {*} message 1044 | * The message sent by the other end of the channel. 1045 | * @param {object} sender 1046 | * Details about the sender of the message. 1047 | * @param {function(*)} sendResponse 1048 | * A callback which, when called with an arbitrary argument, sends 1049 | * that value as a response. 1050 | * @returns {boolean} 1051 | * True if the wrapped listener returned a Promise, which will later 1052 | * yield a response. False otherwise. 1053 | */ 1054 | return function onMessage(message, sender, sendResponse) { 1055 | let didCallSendResponse = false; 1056 | 1057 | let wrappedSendResponse; 1058 | let sendResponsePromise = new Promise(resolve => { 1059 | wrappedSendResponse = function (response) { 1060 | if (!loggedSendResponseDeprecationWarning) { 1061 | console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack); 1062 | loggedSendResponseDeprecationWarning = true; 1063 | } 1064 | didCallSendResponse = true; 1065 | resolve(response); 1066 | }; 1067 | }); 1068 | 1069 | let result; 1070 | try { 1071 | result = listener(message, sender, wrappedSendResponse); 1072 | } catch (err) { 1073 | result = Promise.reject(err); 1074 | } 1075 | 1076 | const isResultThenable = result !== true && isThenable(result); 1077 | 1078 | // If the listener didn't returned true or a Promise, or called 1079 | // wrappedSendResponse synchronously, we can exit earlier 1080 | // because there will be no response sent from this listener. 1081 | if (result !== true && !isResultThenable && !didCallSendResponse) { 1082 | return false; 1083 | } 1084 | 1085 | // A small helper to send the message if the promise resolves 1086 | // and an error if the promise rejects (a wrapped sendMessage has 1087 | // to translate the message into a resolved promise or a rejected 1088 | // promise). 1089 | const sendPromisedResult = promise => { 1090 | promise.then(msg => { 1091 | // send the message value. 1092 | sendResponse(msg); 1093 | }, error => { 1094 | // Send a JSON representation of the error if the rejected value 1095 | // is an instance of error, or the object itself otherwise. 1096 | let message; 1097 | if (error && (error instanceof Error || typeof error.message === "string")) { 1098 | message = error.message; 1099 | } else { 1100 | message = "An unexpected error occurred"; 1101 | } 1102 | 1103 | sendResponse({ 1104 | __mozWebExtensionPolyfillReject__: true, 1105 | message 1106 | }); 1107 | }).catch(err => { 1108 | // Print an error on the console if unable to send the response. 1109 | console.error("Failed to send onMessage rejected reply", err); 1110 | }); 1111 | }; 1112 | 1113 | // If the listener returned a Promise, send the resolved value as a 1114 | // result, otherwise wait the promise related to the wrappedSendResponse 1115 | // callback to resolve and send it as a response. 1116 | if (isResultThenable) { 1117 | sendPromisedResult(result); 1118 | } else { 1119 | sendPromisedResult(sendResponsePromise); 1120 | } 1121 | 1122 | // Let Chrome know that the listener is replying. 1123 | return true; 1124 | }; 1125 | }); 1126 | 1127 | const wrappedSendMessageCallback = ({ reject, resolve }, reply) => { 1128 | if (extensionAPIs.runtime.lastError) { 1129 | // Detect when none of the listeners replied to the sendMessage call and resolve 1130 | // the promise to undefined as in Firefox. 1131 | // See https://github.com/mozilla/webextension-polyfill/issues/130 1132 | if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) { 1133 | resolve(); 1134 | } else { 1135 | reject(extensionAPIs.runtime.lastError); 1136 | } 1137 | } else if (reply && reply.__mozWebExtensionPolyfillReject__) { 1138 | // Convert back the JSON representation of the error into 1139 | // an Error instance. 1140 | reject(new Error(reply.message)); 1141 | } else { 1142 | resolve(reply); 1143 | } 1144 | }; 1145 | 1146 | const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => { 1147 | if (args.length < metadata.minArgs) { 1148 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`); 1149 | } 1150 | 1151 | if (args.length > metadata.maxArgs) { 1152 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`); 1153 | } 1154 | 1155 | return new Promise((resolve, reject) => { 1156 | const wrappedCb = wrappedSendMessageCallback.bind(null, { resolve, reject }); 1157 | args.push(wrappedCb); 1158 | apiNamespaceObj.sendMessage(...args); 1159 | }); 1160 | }; 1161 | 1162 | const staticWrappers = { 1163 | runtime: { 1164 | onMessage: wrapEvent(onMessageWrappers), 1165 | onMessageExternal: wrapEvent(onMessageWrappers), 1166 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 1, maxArgs: 3 }) 1167 | }, 1168 | tabs: { 1169 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", { minArgs: 2, maxArgs: 3 }) 1170 | } 1171 | }; 1172 | const settingMetadata = { 1173 | clear: { minArgs: 1, maxArgs: 1 }, 1174 | get: { minArgs: 1, maxArgs: 1 }, 1175 | set: { minArgs: 1, maxArgs: 1 } 1176 | }; 1177 | apiMetadata.privacy = { 1178 | network: { 1179 | networkPredictionEnabled: settingMetadata, 1180 | webRTCIPHandlingPolicy: settingMetadata 1181 | }, 1182 | services: { 1183 | passwordSavingEnabled: settingMetadata 1184 | }, 1185 | websites: { 1186 | hyperlinkAuditingEnabled: settingMetadata, 1187 | referrersEnabled: settingMetadata 1188 | } 1189 | }; 1190 | 1191 | return wrapObject(extensionAPIs, staticWrappers, apiMetadata); 1192 | }; 1193 | 1194 | if (typeof chrome != "object" || !chrome || !chrome.runtime || !chrome.runtime.id) { 1195 | throw new Error("This script should only be loaded in a browser extension."); 1196 | } 1197 | 1198 | // The build process adds a UMD wrapper around this file, which makes the 1199 | // `module` variable available. 1200 | module.exports = wrapAPIs(chrome); 1201 | } else { 1202 | module.exports = browser; 1203 | } 1204 | }); 1205 | //# sourceMappingURL=browser-polyfill.js.map 1206 | ""; 1207 | }).bind(injectionContext)(); 1208 | var browser = injectionContext.browser; 1209 | var signals = JSON.parse('{"SIGN_CHANGE":"SIGN_CHANGE","SIGN_RELOAD":"SIGN_RELOAD","SIGN_RELOADED":"SIGN_RELOADED","SIGN_LOG":"SIGN_LOG","SIGN_CONNECT":"SIGN_CONNECT"}'); 1210 | var config = JSON.parse('{"RECONNECT_INTERVAL":2000,"SOCKET_ERR_CODE_REF":"https://tools.ietf.org/html/rfc6455#section-7.4.1"}'); 1211 | var reloadPage = "true" === "true"; 1212 | var wsHost = "ws://localhost:9090"; 1213 | var SIGN_CHANGE = signals.SIGN_CHANGE, 1214 | SIGN_RELOAD = signals.SIGN_RELOAD, 1215 | SIGN_RELOADED = signals.SIGN_RELOADED, 1216 | SIGN_LOG = signals.SIGN_LOG, 1217 | SIGN_CONNECT = signals.SIGN_CONNECT; 1218 | var RECONNECT_INTERVAL = config.RECONNECT_INTERVAL, 1219 | SOCKET_ERR_CODE_REF = config.SOCKET_ERR_CODE_REF; 1220 | var extension = browser.extension, 1221 | runtime = browser.runtime, 1222 | tabs = browser.tabs; 1223 | var manifest = runtime.getManifest(); // =============================== Helper functions ======================================= // 1224 | 1225 | var formatter = function formatter(msg) { 1226 | return "[ WER: ".concat(msg, " ]"); 1227 | }; 1228 | 1229 | var logger = function logger(msg) { 1230 | var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "info"; 1231 | return console[level](formatter(msg)); 1232 | }; 1233 | 1234 | var timeFormatter = function timeFormatter(date) { 1235 | return date.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1"); 1236 | }; // ========================== Called only on content scripts ============================== // 1237 | 1238 | 1239 | function contentScriptWorker() { 1240 | runtime.sendMessage({ 1241 | type: SIGN_CONNECT 1242 | }).then(function (msg) { 1243 | return console.info(msg); 1244 | }); 1245 | runtime.onMessage.addListener(function (_ref) { 1246 | var type = _ref.type, 1247 | payload = _ref.payload; 1248 | 1249 | switch (type) { 1250 | case SIGN_RELOAD: 1251 | logger("Detected Changes. Reloading ..."); 1252 | reloadPage && window.location.reload(); 1253 | break; 1254 | 1255 | case SIGN_LOG: 1256 | console.info(payload); 1257 | break; 1258 | } 1259 | }); 1260 | } // ======================== Called only on background scripts ============================= // 1261 | 1262 | 1263 | function backgroundWorker(socket) { 1264 | runtime.onMessage.addListener(function (action, sender) { 1265 | if (action.type === SIGN_CONNECT) { 1266 | return Promise.resolve(formatter("Connected to Extension Hot Reloader")); 1267 | } 1268 | 1269 | return true; 1270 | }); 1271 | socket.addEventListener("message", function (_ref2) { 1272 | var data = _ref2.data; 1273 | 1274 | var _JSON$parse = JSON.parse(data), 1275 | type = _JSON$parse.type, 1276 | payload = _JSON$parse.payload; 1277 | 1278 | if (type === SIGN_CHANGE && (!payload || !payload.onlyPageChanged)) { 1279 | tabs.query({ 1280 | status: "complete" 1281 | }).then(function (loadedTabs) { 1282 | loadedTabs.forEach(function (tab) { 1283 | return tab.id && tabs.sendMessage(tab.id, { 1284 | type: SIGN_RELOAD 1285 | }); 1286 | }); 1287 | socket.send(JSON.stringify({ 1288 | type: SIGN_RELOADED, 1289 | payload: formatter("".concat(timeFormatter(new Date()), " - ").concat(manifest.name, " successfully reloaded")) 1290 | })); 1291 | runtime.reload(); 1292 | }); 1293 | } else { 1294 | runtime.sendMessage({ 1295 | type: type, 1296 | payload: payload 1297 | }); 1298 | } 1299 | }); 1300 | socket.addEventListener("close", function (_ref3) { 1301 | var code = _ref3.code; 1302 | logger("Socket connection closed. Code ".concat(code, ". See more in ").concat(SOCKET_ERR_CODE_REF), "warn"); 1303 | var intId = setInterval(function () { 1304 | logger("Attempting to reconnect (tip: Check if Webpack is running)"); 1305 | var ws = new WebSocket(wsHost); 1306 | 1307 | ws.onerror = function () { 1308 | return logger("Error trying to re-connect. Reattempting in ".concat(RECONNECT_INTERVAL / 1000, "s"), "warn"); 1309 | }; 1310 | 1311 | ws.addEventListener("open", function () { 1312 | clearInterval(intId); 1313 | logger("Reconnected. Reloading plugin"); 1314 | runtime.reload(); 1315 | }); 1316 | }, RECONNECT_INTERVAL); 1317 | }); 1318 | } // ======================== Called only on extension pages that are not the background ============================= // 1319 | 1320 | 1321 | function extensionPageWorker() { 1322 | runtime.sendMessage({ 1323 | type: SIGN_CONNECT 1324 | }).then(function (msg) { 1325 | return console.info(msg); 1326 | }); 1327 | runtime.onMessage.addListener(function (_ref4) { 1328 | var type = _ref4.type, 1329 | payload = _ref4.payload; 1330 | 1331 | switch (type) { 1332 | case SIGN_CHANGE: 1333 | logger("Detected Changes. Reloading ..."); 1334 | reloadPage && window.location.reload(); 1335 | break; 1336 | 1337 | case SIGN_LOG: 1338 | console.info(payload); 1339 | break; 1340 | } 1341 | }); 1342 | } // ======================= Bootstraps the middleware =========================== // 1343 | 1344 | 1345 | runtime.reload ? extension.getBackgroundPage() === window ? backgroundWorker(new WebSocket(wsHost)) : extensionPageWorker() : contentScriptWorker(); 1346 | })(window); 1347 | /* ----------------------------------------------- */ 1348 | 1349 | /* End of Webpack Hot Extension Middleware */ 1350 | 1351 | /* ----------------------------------------------- *//******/ (function(modules) { // webpackBootstrap 1352 | /******/ // The module cache 1353 | /******/ var installedModules = {}; 1354 | /******/ 1355 | /******/ // The require function 1356 | /******/ function __webpack_require__(moduleId) { 1357 | /******/ 1358 | /******/ // Check if module is in cache 1359 | /******/ if(installedModules[moduleId]) { 1360 | /******/ return installedModules[moduleId].exports; 1361 | /******/ } 1362 | /******/ // Create a new module (and put it into the cache) 1363 | /******/ var module = installedModules[moduleId] = { 1364 | /******/ i: moduleId, 1365 | /******/ l: false, 1366 | /******/ exports: {} 1367 | /******/ }; 1368 | /******/ 1369 | /******/ // Execute the module function 1370 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 1371 | /******/ 1372 | /******/ // Flag the module as loaded 1373 | /******/ module.l = true; 1374 | /******/ 1375 | /******/ // Return the exports of the module 1376 | /******/ return module.exports; 1377 | /******/ } 1378 | /******/ 1379 | /******/ 1380 | /******/ // expose the modules object (__webpack_modules__) 1381 | /******/ __webpack_require__.m = modules; 1382 | /******/ 1383 | /******/ // expose the module cache 1384 | /******/ __webpack_require__.c = installedModules; 1385 | /******/ 1386 | /******/ // define getter function for harmony exports 1387 | /******/ __webpack_require__.d = function(exports, name, getter) { 1388 | /******/ if(!__webpack_require__.o(exports, name)) { 1389 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 1390 | /******/ } 1391 | /******/ }; 1392 | /******/ 1393 | /******/ // define __esModule on exports 1394 | /******/ __webpack_require__.r = function(exports) { 1395 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 1396 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 1397 | /******/ } 1398 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 1399 | /******/ }; 1400 | /******/ 1401 | /******/ // create a fake namespace object 1402 | /******/ // mode & 1: value is a module id, require it 1403 | /******/ // mode & 2: merge all properties of value into the ns 1404 | /******/ // mode & 4: return value when already ns object 1405 | /******/ // mode & 8|1: behave like require 1406 | /******/ __webpack_require__.t = function(value, mode) { 1407 | /******/ if(mode & 1) value = __webpack_require__(value); 1408 | /******/ if(mode & 8) return value; 1409 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 1410 | /******/ var ns = Object.create(null); 1411 | /******/ __webpack_require__.r(ns); 1412 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 1413 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 1414 | /******/ return ns; 1415 | /******/ }; 1416 | /******/ 1417 | /******/ // getDefaultExport function for compatibility with non-harmony modules 1418 | /******/ __webpack_require__.n = function(module) { 1419 | /******/ var getter = module && module.__esModule ? 1420 | /******/ function getDefault() { return module['default']; } : 1421 | /******/ function getModuleExports() { return module; }; 1422 | /******/ __webpack_require__.d(getter, 'a', getter); 1423 | /******/ return getter; 1424 | /******/ }; 1425 | /******/ 1426 | /******/ // Object.prototype.hasOwnProperty.call 1427 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 1428 | /******/ 1429 | /******/ // __webpack_public_path__ 1430 | /******/ __webpack_require__.p = ""; 1431 | /******/ 1432 | /******/ 1433 | /******/ // Load entry module and return exports 1434 | /******/ return __webpack_require__(__webpack_require__.s = "./background.js"); 1435 | /******/ }) 1436 | /************************************************************************/ 1437 | /******/ ({ 1438 | 1439 | /***/ "../node_modules/webextension-polyfill/dist/browser-polyfill.js": 1440 | /*!**********************************************************************!*\ 1441 | !*** ../node_modules/webextension-polyfill/dist/browser-polyfill.js ***! 1442 | \**********************************************************************/ 1443 | /*! no static exports found */ 1444 | /***/ (function(module, exports, __webpack_require__) { 1445 | 1446 | eval("var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) {\n if (true) {\n !(__WEBPACK_AMD_DEFINE_ARRAY__ = [module], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?\n\t\t\t\t(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),\n\t\t\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n } else { var mod; }\n})(this, function (module) {\n /* webextension-polyfill - v0.3.1 - Tue Aug 21 2018 10:09:34 */\n /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */\n /* vim: set sts=2 sw=2 et tw=80: */\n /* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n \"use strict\";\n\n if (typeof browser === \"undefined\" || Object.getPrototypeOf(browser) !== Object.prototype) {\n const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = \"The message port closed before a response was received.\";\n const SEND_RESPONSE_DEPRECATION_WARNING = \"Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)\";\n\n // Wrapping the bulk of this polyfill in a one-time-use function is a minor\n // optimization for Firefox. Since Spidermonkey does not fully parse the\n // contents of a function until the first time it's called, and since it will\n // never actually need to be called, this allows the polyfill to be included\n // in Firefox nearly for free.\n const wrapAPIs = () => {\n // NOTE: apiMetadata is associated to the content of the api-metadata.json file\n // at build time by replacing the following \"include\" with the content of the\n // JSON file.\n const apiMetadata = {\n \"alarms\": {\n \"clear\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"clearAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"bookmarks\": {\n \"create\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getChildren\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getRecent\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getSubTree\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getTree\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"move\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeTree\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"search\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n },\n \"browserAction\": {\n \"disable\": {\n \"minArgs\": 0,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"enable\": {\n \"minArgs\": 0,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"getBadgeBackgroundColor\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getBadgeText\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"openPopup\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"setBadgeBackgroundColor\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setBadgeText\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setIcon\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"setPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n }\n },\n \"browsingData\": {\n \"remove\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"removeCache\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeCookies\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeDownloads\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeFormData\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeHistory\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeLocalStorage\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removePasswords\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removePluginData\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"settings\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"commands\": {\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"contextMenus\": {\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n },\n \"cookies\": {\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAllCookieStores\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"set\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"devtools\": {\n \"inspectedWindow\": {\n \"eval\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n }\n },\n \"panels\": {\n \"create\": {\n \"minArgs\": 3,\n \"maxArgs\": 3,\n \"singleCallbackArg\": true\n }\n }\n },\n \"downloads\": {\n \"cancel\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"download\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"erase\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getFileIcon\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"open\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"pause\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeFile\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"resume\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"search\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"show\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n }\n },\n \"extension\": {\n \"isAllowedFileSchemeAccess\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"isAllowedIncognitoAccess\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"history\": {\n \"addUrl\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"deleteAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"deleteRange\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"deleteUrl\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getVisits\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"search\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"i18n\": {\n \"detectLanguage\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAcceptLanguages\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"identity\": {\n \"launchWebAuthFlow\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"idle\": {\n \"queryState\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"management\": {\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getSelf\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"setEnabled\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"uninstallSelf\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n }\n },\n \"notifications\": {\n \"clear\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"create\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getPermissionLevel\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n },\n \"pageAction\": {\n \"getPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"hide\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setIcon\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"setPopup\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"setTitle\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n },\n \"show\": {\n \"minArgs\": 1,\n \"maxArgs\": 1,\n \"fallbackToNoCallback\": true\n }\n },\n \"permissions\": {\n \"contains\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"request\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"runtime\": {\n \"getBackgroundPage\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getBrowserInfo\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getPlatformInfo\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"openOptionsPage\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"requestUpdateCheck\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"sendMessage\": {\n \"minArgs\": 1,\n \"maxArgs\": 3\n },\n \"sendNativeMessage\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"setUninstallURL\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"sessions\": {\n \"getDevices\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getRecentlyClosed\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"restore\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n }\n },\n \"storage\": {\n \"local\": {\n \"clear\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getBytesInUse\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"set\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"managed\": {\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getBytesInUse\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n }\n },\n \"sync\": {\n \"clear\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getBytesInUse\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"set\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n }\n },\n \"tabs\": {\n \"captureVisibleTab\": {\n \"minArgs\": 0,\n \"maxArgs\": 2\n },\n \"create\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"detectLanguage\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"discard\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"duplicate\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"executeScript\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getCurrent\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n },\n \"getZoom\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getZoomSettings\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"highlight\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"insertCSS\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"move\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n },\n \"query\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"reload\": {\n \"minArgs\": 0,\n \"maxArgs\": 2\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"removeCSS\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"sendMessage\": {\n \"minArgs\": 2,\n \"maxArgs\": 3\n },\n \"setZoom\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"setZoomSettings\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"update\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n }\n },\n \"topSites\": {\n \"get\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"webNavigation\": {\n \"getAllFrames\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"getFrame\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n }\n },\n \"webRequest\": {\n \"handlerBehaviorChanged\": {\n \"minArgs\": 0,\n \"maxArgs\": 0\n }\n },\n \"windows\": {\n \"create\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"get\": {\n \"minArgs\": 1,\n \"maxArgs\": 2\n },\n \"getAll\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getCurrent\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"getLastFocused\": {\n \"minArgs\": 0,\n \"maxArgs\": 1\n },\n \"remove\": {\n \"minArgs\": 1,\n \"maxArgs\": 1\n },\n \"update\": {\n \"minArgs\": 2,\n \"maxArgs\": 2\n }\n }\n };\n\n if (Object.keys(apiMetadata).length === 0) {\n throw new Error(\"api-metadata.json has not been included in browser-polyfill\");\n }\n\n /**\n * A WeakMap subclass which creates and stores a value for any key which does\n * not exist when accessed, but behaves exactly as an ordinary WeakMap\n * otherwise.\n *\n * @param {function} createItem\n * A function which will be called in order to create the value for any\n * key which does not exist, the first time it is accessed. The\n * function receives, as its only argument, the key being created.\n */\n class DefaultWeakMap extends WeakMap {\n constructor(createItem, items = undefined) {\n super(items);\n this.createItem = createItem;\n }\n\n get(key) {\n if (!this.has(key)) {\n this.set(key, this.createItem(key));\n }\n\n return super.get(key);\n }\n }\n\n /**\n * Returns true if the given object is an object with a `then` method, and can\n * therefore be assumed to behave as a Promise.\n *\n * @param {*} value The value to test.\n * @returns {boolean} True if the value is thenable.\n */\n const isThenable = value => {\n return value && typeof value === \"object\" && typeof value.then === \"function\";\n };\n\n /**\n * Creates and returns a function which, when called, will resolve or reject\n * the given promise based on how it is called:\n *\n * - If, when called, `chrome.runtime.lastError` contains a non-null object,\n * the promise is rejected with that value.\n * - If the function is called with exactly one argument, the promise is\n * resolved to that value.\n * - Otherwise, the promise is resolved to an array containing all of the\n * function's arguments.\n *\n * @param {object} promise\n * An object containing the resolution and rejection functions of a\n * promise.\n * @param {function} promise.resolve\n * The promise's resolution function.\n * @param {function} promise.rejection\n * The promise's rejection function.\n * @param {object} metadata\n * Metadata about the wrapped method which has created the callback.\n * @param {integer} metadata.maxResolvedArgs\n * The maximum number of arguments which may be passed to the\n * callback created by the wrapped async function.\n *\n * @returns {function}\n * The generated callback function.\n */\n const makeCallback = (promise, metadata) => {\n return (...callbackArgs) => {\n if (chrome.runtime.lastError) {\n promise.reject(chrome.runtime.lastError);\n } else if (metadata.singleCallbackArg || callbackArgs.length <= 1) {\n promise.resolve(callbackArgs[0]);\n } else {\n promise.resolve(callbackArgs);\n }\n };\n };\n\n const pluralizeArguments = numArgs => numArgs == 1 ? \"argument\" : \"arguments\";\n\n /**\n * Creates a wrapper function for a method with the given name and metadata.\n *\n * @param {string} name\n * The name of the method which is being wrapped.\n * @param {object} metadata\n * Metadata about the method being wrapped.\n * @param {integer} metadata.minArgs\n * The minimum number of arguments which must be passed to the\n * function. If called with fewer than this number of arguments, the\n * wrapper will raise an exception.\n * @param {integer} metadata.maxArgs\n * The maximum number of arguments which may be passed to the\n * function. If called with more than this number of arguments, the\n * wrapper will raise an exception.\n * @param {integer} metadata.maxResolvedArgs\n * The maximum number of arguments which may be passed to the\n * callback created by the wrapped async function.\n *\n * @returns {function(object, ...*)}\n * The generated wrapper function.\n */\n const wrapAsyncFunction = (name, metadata) => {\n return function asyncFunctionWrapper(target, ...args) {\n if (args.length < metadata.minArgs) {\n throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);\n }\n\n if (args.length > metadata.maxArgs) {\n throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);\n }\n\n return new Promise((resolve, reject) => {\n if (metadata.fallbackToNoCallback) {\n // This API method has currently no callback on Chrome, but it return a promise on Firefox,\n // and so the polyfill will try to call it with a callback first, and it will fallback\n // to not passing the callback if the first call fails.\n try {\n target[name](...args, makeCallback({ resolve, reject }, metadata));\n } catch (cbError) {\n console.warn(`${name} API method doesn't seem to support the callback parameter, ` + \"falling back to call it without a callback: \", cbError);\n\n target[name](...args);\n\n // Update the API method metadata, so that the next API calls will not try to\n // use the unsupported callback anymore.\n metadata.fallbackToNoCallback = false;\n metadata.noCallback = true;\n\n resolve();\n }\n } else if (metadata.noCallback) {\n target[name](...args);\n resolve();\n } else {\n target[name](...args, makeCallback({ resolve, reject }, metadata));\n }\n });\n };\n };\n\n /**\n * Wraps an existing method of the target object, so that calls to it are\n * intercepted by the given wrapper function. The wrapper function receives,\n * as its first argument, the original `target` object, followed by each of\n * the arguments passed to the original method.\n *\n * @param {object} target\n * The original target object that the wrapped method belongs to.\n * @param {function} method\n * The method being wrapped. This is used as the target of the Proxy\n * object which is created to wrap the method.\n * @param {function} wrapper\n * The wrapper function which is called in place of a direct invocation\n * of the wrapped method.\n *\n * @returns {Proxy}\n * A Proxy object for the given method, which invokes the given wrapper\n * method in its place.\n */\n const wrapMethod = (target, method, wrapper) => {\n return new Proxy(method, {\n apply(targetMethod, thisObj, args) {\n return wrapper.call(thisObj, target, ...args);\n }\n });\n };\n\n let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);\n\n /**\n * Wraps an object in a Proxy which intercepts and wraps certain methods\n * based on the given `wrappers` and `metadata` objects.\n *\n * @param {object} target\n * The target object to wrap.\n *\n * @param {object} [wrappers = {}]\n * An object tree containing wrapper functions for special cases. Any\n * function present in this object tree is called in place of the\n * method in the same location in the `target` object tree. These\n * wrapper methods are invoked as described in {@see wrapMethod}.\n *\n * @param {object} [metadata = {}]\n * An object tree containing metadata used to automatically generate\n * Promise-based wrapper functions for asynchronous. Any function in\n * the `target` object tree which has a corresponding metadata object\n * in the same location in the `metadata` tree is replaced with an\n * automatically-generated wrapper function, as described in\n * {@see wrapAsyncFunction}\n *\n * @returns {Proxy}\n */\n const wrapObject = (target, wrappers = {}, metadata = {}) => {\n let cache = Object.create(null);\n let handlers = {\n has(proxyTarget, prop) {\n return prop in target || prop in cache;\n },\n\n get(proxyTarget, prop, receiver) {\n if (prop in cache) {\n return cache[prop];\n }\n\n if (!(prop in target)) {\n return undefined;\n }\n\n let value = target[prop];\n\n if (typeof value === \"function\") {\n // This is a method on the underlying object. Check if we need to do\n // any wrapping.\n\n if (typeof wrappers[prop] === \"function\") {\n // We have a special-case wrapper for this method.\n value = wrapMethod(target, target[prop], wrappers[prop]);\n } else if (hasOwnProperty(metadata, prop)) {\n // This is an async method that we have metadata for. Create a\n // Promise wrapper for it.\n let wrapper = wrapAsyncFunction(prop, metadata[prop]);\n value = wrapMethod(target, target[prop], wrapper);\n } else {\n // This is a method that we don't know or care about. Return the\n // original method, bound to the underlying object.\n value = value.bind(target);\n }\n } else if (typeof value === \"object\" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) {\n // This is an object that we need to do some wrapping for the children\n // of. Create a sub-object wrapper for it with the appropriate child\n // metadata.\n value = wrapObject(value, wrappers[prop], metadata[prop]);\n } else {\n // We don't need to do any wrapping for this property,\n // so just forward all access to the underlying object.\n Object.defineProperty(cache, prop, {\n configurable: true,\n enumerable: true,\n get() {\n return target[prop];\n },\n set(value) {\n target[prop] = value;\n }\n });\n\n return value;\n }\n\n cache[prop] = value;\n return value;\n },\n\n set(proxyTarget, prop, value, receiver) {\n if (prop in cache) {\n cache[prop] = value;\n } else {\n target[prop] = value;\n }\n return true;\n },\n\n defineProperty(proxyTarget, prop, desc) {\n return Reflect.defineProperty(cache, prop, desc);\n },\n\n deleteProperty(proxyTarget, prop) {\n return Reflect.deleteProperty(cache, prop);\n }\n };\n\n // Per contract of the Proxy API, the \"get\" proxy handler must return the\n // original value of the target if that value is declared read-only and\n // non-configurable. For this reason, we create an object with the\n // prototype set to `target` instead of using `target` directly.\n // Otherwise we cannot return a custom object for APIs that\n // are declared read-only and non-configurable, such as `chrome.devtools`.\n //\n // The proxy handlers themselves will still use the original `target`\n // instead of the `proxyTarget`, so that the methods and properties are\n // dereferenced via the original targets.\n let proxyTarget = Object.create(target);\n return new Proxy(proxyTarget, handlers);\n };\n\n /**\n * Creates a set of wrapper functions for an event object, which handles\n * wrapping of listener functions that those messages are passed.\n *\n * A single wrapper is created for each listener function, and stored in a\n * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener`\n * retrieve the original wrapper, so that attempts to remove a\n * previously-added listener work as expected.\n *\n * @param {DefaultWeakMap} wrapperMap\n * A DefaultWeakMap object which will create the appropriate wrapper\n * for a given listener function when one does not exist, and retrieve\n * an existing one when it does.\n *\n * @returns {object}\n */\n const wrapEvent = wrapperMap => ({\n addListener(target, listener, ...args) {\n target.addListener(wrapperMap.get(listener), ...args);\n },\n\n hasListener(target, listener) {\n return target.hasListener(wrapperMap.get(listener));\n },\n\n removeListener(target, listener) {\n target.removeListener(wrapperMap.get(listener));\n }\n });\n\n // Keep track if the deprecation warning has been logged at least once.\n let loggedSendResponseDeprecationWarning = false;\n\n const onMessageWrappers = new DefaultWeakMap(listener => {\n if (typeof listener !== \"function\") {\n return listener;\n }\n\n /**\n * Wraps a message listener function so that it may send responses based on\n * its return value, rather than by returning a sentinel value and calling a\n * callback. If the listener function returns a Promise, the response is\n * sent when the promise either resolves or rejects.\n *\n * @param {*} message\n * The message sent by the other end of the channel.\n * @param {object} sender\n * Details about the sender of the message.\n * @param {function(*)} sendResponse\n * A callback which, when called with an arbitrary argument, sends\n * that value as a response.\n * @returns {boolean}\n * True if the wrapped listener returned a Promise, which will later\n * yield a response. False otherwise.\n */\n return function onMessage(message, sender, sendResponse) {\n let didCallSendResponse = false;\n\n let wrappedSendResponse;\n let sendResponsePromise = new Promise(resolve => {\n wrappedSendResponse = function (response) {\n if (!loggedSendResponseDeprecationWarning) {\n console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);\n loggedSendResponseDeprecationWarning = true;\n }\n didCallSendResponse = true;\n resolve(response);\n };\n });\n\n let result;\n try {\n result = listener(message, sender, wrappedSendResponse);\n } catch (err) {\n result = Promise.reject(err);\n }\n\n const isResultThenable = result !== true && isThenable(result);\n\n // If the listener didn't returned true or a Promise, or called\n // wrappedSendResponse synchronously, we can exit earlier\n // because there will be no response sent from this listener.\n if (result !== true && !isResultThenable && !didCallSendResponse) {\n return false;\n }\n\n // A small helper to send the message if the promise resolves\n // and an error if the promise rejects (a wrapped sendMessage has\n // to translate the message into a resolved promise or a rejected\n // promise).\n const sendPromisedResult = promise => {\n promise.then(msg => {\n // send the message value.\n sendResponse(msg);\n }, error => {\n // Send a JSON representation of the error if the rejected value\n // is an instance of error, or the object itself otherwise.\n let message;\n if (error && (error instanceof Error || typeof error.message === \"string\")) {\n message = error.message;\n } else {\n message = \"An unexpected error occurred\";\n }\n\n sendResponse({\n __mozWebExtensionPolyfillReject__: true,\n message\n });\n }).catch(err => {\n // Print an error on the console if unable to send the response.\n console.error(\"Failed to send onMessage rejected reply\", err);\n });\n };\n\n // If the listener returned a Promise, send the resolved value as a\n // result, otherwise wait the promise related to the wrappedSendResponse\n // callback to resolve and send it as a response.\n if (isResultThenable) {\n sendPromisedResult(result);\n } else {\n sendPromisedResult(sendResponsePromise);\n }\n\n // Let Chrome know that the listener is replying.\n return true;\n };\n });\n\n const wrappedSendMessageCallback = ({ reject, resolve }, reply) => {\n if (chrome.runtime.lastError) {\n // Detect when none of the listeners replied to the sendMessage call and resolve\n // the promise to undefined as in Firefox.\n // See https://github.com/mozilla/webextension-polyfill/issues/130\n if (chrome.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {\n resolve();\n } else {\n reject(chrome.runtime.lastError);\n }\n } else if (reply && reply.__mozWebExtensionPolyfillReject__) {\n // Convert back the JSON representation of the error into\n // an Error instance.\n reject(new Error(reply.message));\n } else {\n resolve(reply);\n }\n };\n\n const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {\n if (args.length < metadata.minArgs) {\n throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);\n }\n\n if (args.length > metadata.maxArgs) {\n throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);\n }\n\n return new Promise((resolve, reject) => {\n const wrappedCb = wrappedSendMessageCallback.bind(null, { resolve, reject });\n args.push(wrappedCb);\n apiNamespaceObj.sendMessage(...args);\n });\n };\n\n const staticWrappers = {\n runtime: {\n onMessage: wrapEvent(onMessageWrappers),\n onMessageExternal: wrapEvent(onMessageWrappers),\n sendMessage: wrappedSendMessage.bind(null, \"sendMessage\", { minArgs: 1, maxArgs: 3 })\n },\n tabs: {\n sendMessage: wrappedSendMessage.bind(null, \"sendMessage\", { minArgs: 2, maxArgs: 3 })\n }\n };\n const settingMetadata = {\n clear: { minArgs: 1, maxArgs: 1 },\n get: { minArgs: 1, maxArgs: 1 },\n set: { minArgs: 1, maxArgs: 1 }\n };\n apiMetadata.privacy = {\n network: {\n networkPredictionEnabled: settingMetadata,\n webRTCIPHandlingPolicy: settingMetadata\n },\n services: {\n passwordSavingEnabled: settingMetadata\n },\n websites: {\n hyperlinkAuditingEnabled: settingMetadata,\n referrersEnabled: settingMetadata\n }\n };\n\n return wrapObject(chrome, staticWrappers, apiMetadata);\n };\n\n // The build process adds a UMD wrapper around this file, which makes the\n // `module` variable available.\n module.exports = wrapAPIs(); // eslint-disable-line no-undef\n } else {\n module.exports = browser; // eslint-disable-line no-undef\n }\n});\n//# sourceMappingURL=browser-polyfill.js.map\n\n\n//# sourceURL=webpack:///../node_modules/webextension-polyfill/dist/browser-polyfill.js?"); 1447 | 1448 | /***/ }), 1449 | 1450 | /***/ "./background.js": 1451 | /*!***********************!*\ 1452 | !*** ./background.js ***! 1453 | \***********************/ 1454 | /*! no static exports found */ 1455 | /***/ (function(module, exports, __webpack_require__) { 1456 | 1457 | eval("window.browser = __webpack_require__(/*! webextension-polyfill */ \"../node_modules/webextension-polyfill/dist/browser-polyfill.js\");\n\nfunction sendMessageToContentScript(message) {\n chrome.tabs.query({\n active: true,\n currentWindow: true\n }, function (tabs) {\n chrome.tabs.sendMessage(tabs[0].id, message);\n });\n}\n\nvar textHandler = function textHandler() {\n sendMessageToContentScript({\n type: 1\n });\n};\n\nvar textHandler2 = function textHandler2() {\n sendMessageToContentScript({\n type: 2\n });\n};\n\nvar imageHandler = function imageHandler() {\n sendMessageToContentScript({\n type: 3\n });\n};\n\nvar routeToHome = function routeToHome() {\n window.open('https://github.com/thegreatjavascript/FakeScreenshot');\n};\n\nvar routeToOptions = function routeToOptions(isHelp) {\n var options = chrome.runtime.getURL(\"options/options.html\".concat(isHelp ? '?help=1' : ''));\n window.open(options);\n};\n\nvar options = {\n type: 'normal',\n id: '1',\n title: '修改文字(就地)',\n visible: true,\n contexts: ['all'],\n onclick: textHandler\n};\nchrome.contextMenus.create(options);\nvar options2 = {\n type: 'normal',\n id: '2',\n title: '修改文字(弹框)',\n visible: true,\n contexts: ['all'],\n onclick: textHandler2\n};\nchrome.contextMenus.create(options2);\nvar options3 = {\n type: 'normal',\n id: '3',\n title: '修改图片',\n visible: true,\n contexts: ['all'],\n onclick: imageHandler\n};\nchrome.contextMenus.create(options3);\nvar options4 = {\n type: 'separator',\n id: '4'\n};\nchrome.contextMenus.create(options4);\nvar options7 = {\n type: 'normal',\n id: '7',\n title: '截图检测页',\n visible: true,\n contexts: ['all'],\n onclick: function onclick() {\n routeToOptions(0);\n }\n};\nchrome.contextMenus.create(options7);\nvar options6 = {\n type: 'separator',\n id: '6'\n};\nchrome.contextMenus.create(options6);\nvar options5 = {\n type: 'normal',\n id: '5',\n title: '项目主页',\n visible: true,\n contexts: ['all'],\n onclick: routeToHome\n};\nchrome.contextMenus.create(options5);\n\nvar getItem = function getItem(key) {\n return window.localStorage.getItem(key);\n};\n\nvar setItem = function setItem(key, value) {\n return window.localStorage.setItem(key, value);\n}; // 检查是否初始化过。\n\n\nvar inited = getItem('inited'); // 如果是第一次初始化。(重新打开浏览器会触发)\n\nif (inited !== '1') {\n // 打开帮助页面\n routeToOptions(1);\n setItem('inited', '1');\n}\n\n//# sourceURL=webpack:///./background.js?"); 1458 | 1459 | /***/ }) 1460 | 1461 | /******/ }); -------------------------------------------------------------------------------- /dist/icons/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/1.png -------------------------------------------------------------------------------- /dist/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/128.png -------------------------------------------------------------------------------- /dist/icons/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/2.png -------------------------------------------------------------------------------- /dist/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/48.png -------------------------------------------------------------------------------- /dist/icons/FakeScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/FakeScreenshot.png -------------------------------------------------------------------------------- /dist/icons/ic_launcher_APP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/ic_launcher_APP.png -------------------------------------------------------------------------------- /dist/icons/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/icon_128.png -------------------------------------------------------------------------------- /dist/icons/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/icon_48.png -------------------------------------------------------------------------------- /dist/icons/screenshot128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/screenshot128.png -------------------------------------------------------------------------------- /dist/icons/screenshot48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/screenshot48.png -------------------------------------------------------------------------------- /dist/icons/截图 (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/截图 (1).png -------------------------------------------------------------------------------- /dist/icons/截图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/icons/截图.png -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FakeScreenshot", 3 | "description": "FakeScreenshot/虚假截图制作工具:截图 = 实锤?相信你就输了!", 4 | "version": "1.0.0", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "contextMenus", 8 | "tabs", 9 | "activeTab" 10 | ], 11 | "icons": { 12 | "48": "icons/48.png", 13 | "128": "icons/128.png" 14 | }, 15 | "browser_action": { 16 | "default_title": "FakeScreenshot", 17 | "default_popup": "popup/popup.html" 18 | }, 19 | "background": { 20 | "scripts": [ 21 | "background.js" 22 | ] 23 | }, 24 | "content_scripts": [ 25 | { 26 | "matches": [ 27 | "http://*/*", 28 | "https://*/*" 29 | ], 30 | "js": [ 31 | "jquery.js", 32 | "jquery.upload.js", 33 | "page.js" 34 | ] 35 | } 36 | ], 37 | "options_ui": { 38 | "page": "options/options.html", 39 | "chrome_style": true 40 | }, 41 | "web_accessible_resources": [ 42 | "src/options/options.html" 43 | ], 44 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 45 | } -------------------------------------------------------------------------------- /dist/options/options.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-color: #de335e; 3 | --text-color: #606266; 4 | } 5 | *, 6 | body, 7 | h3 { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | .container { 12 | font-family: cursive; 13 | font-size: 14px; 14 | line-height: 1; 15 | } 16 | .container .head { 17 | width: 100%; 18 | height: 60px; 19 | display: flex; 20 | justify-content: flex-end; 21 | font-size: 18px; 22 | } 23 | .container .head div { 24 | width: 60%; 25 | margin: auto; 26 | height: 100%; 27 | display: flex; 28 | align-items: center; 29 | } 30 | .container .head span, 31 | .container .head a { 32 | margin: 0 20px; 33 | display: inline-block; 34 | position: relative; 35 | cursor: pointer; 36 | } 37 | .container .head span.active { 38 | color: var(--main-color); 39 | } 40 | .container .head span.active::after { 41 | position: absolute; 42 | bottom: -24px; 43 | right: 0; 44 | height: 4px; 45 | width: 100%; 46 | background: var(--main-color); 47 | content: ''; 48 | } 49 | .container .head::after { 50 | box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.07); 51 | display: block; 52 | position: absolute; 53 | top: 60px; 54 | color: rgba(0, 0, 0, 0.07); 55 | content: ''; 56 | width: 100%; 57 | height: 2px; 58 | } 59 | .container .help { 60 | margin: 5vh auto; 61 | width: 90vw; 62 | } 63 | .container .help h3 { 64 | color: var(--main-color); 65 | text-align: center; 66 | margin-bottom: 10px; 67 | } 68 | .container .help .content { 69 | display: flex; 70 | justify-content: space-between; 71 | } 72 | .container .help .content .card video { 73 | width: 29vw; 74 | } 75 | .container .help .last { 76 | margin: auto; 77 | width: 50vw; 78 | } 79 | .container .help .last video { 80 | width: 100%; 81 | } 82 | .container h1 { 83 | color: var(--main-color); 84 | font-weight: bold; 85 | font-size: 35px; 86 | text-align: center; 87 | margin-bottom: 50px; 88 | } 89 | .container header { 90 | margin: 5vh auto; 91 | width: 60%; 92 | } 93 | .container header h3 { 94 | font-weight: bold; 95 | font-size: 25px; 96 | font-size: 19px; 97 | margin-top: 10px; 98 | } 99 | .container header h3 span { 100 | font-size: 20px; 101 | margin-right: 3px; 102 | display: inline-block; 103 | } 104 | .container header .top-50 { 105 | margin-top: 50px; 106 | } 107 | .container header strong { 108 | display: inline-block; 109 | margin: 0 2px; 110 | color: var(--main-color); 111 | } 112 | .container header p { 113 | margin: 5px auto; 114 | line-height: 1.8; 115 | } 116 | .container header p.bottom-30 { 117 | margin-bottom: 30px; 118 | } 119 | .container header .big { 120 | display: inline-block; 121 | margin-top: 10px; 122 | font-size: 30px; 123 | color: var(--main-color); 124 | } 125 | .container header .upload { 126 | width: 70px; 127 | height: 70px; 128 | position: relative; 129 | box-shadow: 0 0 5px #eee; 130 | box-sizing: border-box; 131 | padding: 10px; 132 | margin: auto; 133 | } 134 | .container header .upload svg { 135 | position: absolute; 136 | top: 0; 137 | left: 0; 138 | width: 70px; 139 | height: 70px; 140 | box-sizing: border-box; 141 | } 142 | .container header .upload input { 143 | width: 70px; 144 | height: 70px; 145 | opacity: 0; 146 | z-index: 10; 147 | position: absolute; 148 | top: 0; 149 | left: 0; 150 | } 151 | .container section { 152 | width: 100%; 153 | position: relative; 154 | background: black; 155 | mix-blend-mode: color-burn; 156 | } 157 | .container section .preview { 158 | width: 100%; 159 | display: none; 160 | } 161 | .container section .cover { 162 | position: absolute; 163 | left: 0; 164 | top: 0; 165 | background: black; 166 | width: 100%; 167 | height: 100%; 168 | mix-blend-mode: color-burn; 169 | } 170 | .container a { 171 | text-decoration: none; 172 | color: inherit; 173 | } 174 | 175 | -------------------------------------------------------------------------------- /dist/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FakeScreenshot - 检测页 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dist/options/图片.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/options/图片.webm -------------------------------------------------------------------------------- /dist/options/基础.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/options/基础.webm -------------------------------------------------------------------------------- /dist/options/弹框.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/options/弹框.webm -------------------------------------------------------------------------------- /dist/options/检测.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/dist/options/检测.webm -------------------------------------------------------------------------------- /dist/popup/popup.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | :root { 3 | --main-color: #de335e; 4 | --text-color: #606266; 5 | } 6 | * { 7 | padding: 0; 8 | margin: 0; 9 | } 10 | body { 11 | font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; 12 | padding: 10px 20px; 13 | } 14 | h1 { 15 | color: var(--main-color); 16 | font-size: 20px; 17 | padding: 0; 18 | text-align: center; 19 | padding-bottom: 0.5em; 20 | } 21 | blockquote { 22 | padding: 0 1em; 23 | color: var(--text-color); 24 | border-left: 0.25em solid var(--main-color); 25 | } 26 | p { 27 | font-size: 14px; 28 | line-height: 1.6; 29 | word-break: break-all; 30 | } 31 | .route { 32 | cursor: pointer; 33 | } 34 | .m-bottom { 35 | margin-bottom: 10px; 36 | } 37 | .m-bottom-5 { 38 | margin-bottom: 5px; 39 | } 40 | .m-bottom-20 { 41 | margin-bottom: 20px; 42 | } 43 | h3, 44 | .route { 45 | color: var(--main-color); 46 | } 47 | footer { 48 | margin-top: 20px; 49 | } 50 | footer a { 51 | width: fit-content; 52 | display: block; 53 | margin: auto; 54 | } 55 | a { 56 | color: inherit; 57 | text-decoration: none; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /dist/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fakescreenshot", 3 | "version": "1.0.0", 4 | "description": "FakeScreenshot2.0/虚假截图制作工具2.0", 5 | "author": "shuiRong ", 6 | "license": "MIT", 7 | "engines": { 8 | "node": ">=10" 9 | }, 10 | "scripts": { 11 | "prettier": "prettier \"src/**/*.{js,vue}\"", 12 | "prettier:write": "npm run prettier -- --write", 13 | "build": "cross-env NODE_ENV=production webpack --hide-modules", 14 | "build:dev": "cross-env NODE_ENV=development webpack --hide-modules", 15 | "build-zip": "node scripts/build-zip.js", 16 | "watch": "npm run build -- --watch", 17 | "watch:dev": "cross-env HMR=true npm run build:dev -- --watch" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "pretty-quick --staged" 22 | } 23 | }, 24 | "dependencies": { 25 | "vue": "^2.6.10", 26 | "vue-router": "^3.0.1", 27 | "webextension-polyfill": "^0.3.1" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.1.2", 31 | "@babel/plugin-proposal-optional-chaining": "^7.0.0", 32 | "@babel/preset-env": "^7.1.0", 33 | "@babel/runtime-corejs3": "^7.4.0", 34 | "archiver": "^3.0.0", 35 | "babel-loader": "^8.0.2", 36 | "copy-webpack-plugin": "^5.1.1", 37 | "core-js": "^3.0.1", 38 | "cross-env": "^5.2.0", 39 | "css-loader": "^3.4.0", 40 | "ejs": "^2.6.1", 41 | "file-loader": "^5.0.2", 42 | "husky": "^2.4.0", 43 | "mini-css-extract-plugin": "^0.9.0", 44 | "node-sass": "^4.9.3", 45 | "prettier": "^1.17.1", 46 | "pretty-quick": "^1.8.0", 47 | "sass-loader": "^7.1.0", 48 | "vue-loader": "^15.4.2", 49 | "vue-template-compiler": "^2.6.10", 50 | "web-ext-types": "^2.1.0", 51 | "webpack": "^4.20.2", 52 | "webpack-cli": "^3.3.10", 53 | "webpack-extension-reloader": "^1.1.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /preview/basic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/preview/basic.gif -------------------------------------------------------------------------------- /preview/check.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/preview/check.gif -------------------------------------------------------------------------------- /preview/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/preview/check.png -------------------------------------------------------------------------------- /preview/dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/preview/dialog.gif -------------------------------------------------------------------------------- /preview/picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/preview/picture.gif -------------------------------------------------------------------------------- /preview/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/preview/test.png -------------------------------------------------------------------------------- /scripts/build-zip.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const archiver = require('archiver'); 6 | 7 | const DEST_DIR = path.join(__dirname, '../dist'); 8 | const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip'); 9 | 10 | const extractExtensionData = () => { 11 | const extPackageJson = require('../package.json'); 12 | 13 | return { 14 | name: extPackageJson.name, 15 | version: extPackageJson.version 16 | } 17 | }; 18 | 19 | const makeDestZipDirIfNotExists = () => { 20 | if(!fs.existsSync(DEST_ZIP_DIR)) { 21 | fs.mkdirSync(DEST_ZIP_DIR); 22 | } 23 | } 24 | 25 | const buildZip = (src, dist, zipFilename) => { 26 | console.info(`Building ${zipFilename}...`); 27 | 28 | const archive = archiver('zip', { zlib: { level: 9 }}); 29 | const stream = fs.createWriteStream(path.join(dist, zipFilename)); 30 | 31 | return new Promise((resolve, reject) => { 32 | archive 33 | .directory(src, false) 34 | .on('error', err => reject(err)) 35 | .pipe(stream); 36 | 37 | stream.on('close', () => resolve()); 38 | archive.finalize(); 39 | }); 40 | }; 41 | 42 | const main = () => { 43 | const {name, version} = extractExtensionData(); 44 | const zipFilename = `${name}-v${version}.zip`; 45 | 46 | makeDestZipDirIfNotExists(); 47 | 48 | buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename) 49 | .then(() => console.info('OK')) 50 | .catch(console.err); 51 | }; 52 | 53 | main(); 54 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | global.browser = require('webextension-polyfill'); 2 | 3 | function sendMessageToContentScript(message) { 4 | chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { 5 | chrome.tabs.sendMessage(tabs[0].id, message); 6 | }); 7 | } 8 | 9 | const textHandler = () => { 10 | sendMessageToContentScript({ type: 1 }); 11 | }; 12 | 13 | const textHandler2 = () => { 14 | sendMessageToContentScript({ type: 2 }); 15 | }; 16 | 17 | const imageHandler = () => { 18 | sendMessageToContentScript({ type: 3 }); 19 | }; 20 | 21 | const routeToHome = () => { 22 | window.open('https://github.com/thegreatjavascript/FakeScreenshot'); 23 | }; 24 | 25 | const routeToOptions = isHelp => { 26 | const options = chrome.runtime.getURL(`options/options.html${isHelp ? '?help=1' : ''}`); 27 | 28 | window.open(options); 29 | }; 30 | 31 | const options = { 32 | type: 'normal', 33 | id: '1', 34 | title: '修改文字(就地)', 35 | visible: true, 36 | contexts: ['all'], 37 | onclick: textHandler, 38 | }; 39 | chrome.contextMenus.create(options); 40 | const options2 = { 41 | type: 'normal', 42 | id: '2', 43 | title: '修改文字(弹框)', 44 | visible: true, 45 | contexts: ['all'], 46 | onclick: textHandler2, 47 | }; 48 | chrome.contextMenus.create(options2); 49 | 50 | const options3 = { 51 | type: 'normal', 52 | id: '3', 53 | title: '修改图片', 54 | visible: true, 55 | contexts: ['all'], 56 | onclick: imageHandler, 57 | }; 58 | chrome.contextMenus.create(options3); 59 | 60 | const options4 = { 61 | type: 'separator', 62 | id: '4', 63 | }; 64 | chrome.contextMenus.create(options4); 65 | 66 | const options7 = { 67 | type: 'normal', 68 | id: '7', 69 | title: '截图检测页', 70 | visible: true, 71 | contexts: ['all'], 72 | onclick: () => { 73 | routeToOptions(0); 74 | }, 75 | }; 76 | chrome.contextMenus.create(options7); 77 | 78 | const options6 = { 79 | type: 'separator', 80 | id: '6', 81 | }; 82 | chrome.contextMenus.create(options6); 83 | 84 | const options5 = { 85 | type: 'normal', 86 | id: '5', 87 | title: '项目主页', 88 | visible: true, 89 | contexts: ['all'], 90 | onclick: routeToHome, 91 | }; 92 | chrome.contextMenus.create(options5); 93 | 94 | const getItem = key => window.localStorage.getItem(key); 95 | const setItem = (key, value) => window.localStorage.setItem(key, value); 96 | 97 | // 检查是否初始化过。 98 | const inited = getItem('inited'); 99 | // 如果是第一次初始化。(重新打开浏览器会触发) 100 | if (inited !== '1') { 101 | // 打开帮助页面 102 | routeToOptions(1); 103 | setItem('inited', '1'); 104 | } 105 | -------------------------------------------------------------------------------- /src/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/src/icons/128.png -------------------------------------------------------------------------------- /src/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/src/icons/48.png -------------------------------------------------------------------------------- /src/icons/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thegreatjavascript/FakeScreenshot/d1512bbe43ad89ba2be6fb9b5fd1f5ca702450e7/src/icons/icon.xcf -------------------------------------------------------------------------------- /src/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0 { 3 | $dom.css('background-image', `url(${base64})`); 4 | }; 5 | 6 | const initDOM = ({ left, top }) => { 7 | return $(`
8 | `); 48 | }; 49 | 50 | $.fn.image = function(options) { 51 | const _this = this; 52 | 53 | const inputChange = e => { 54 | const files = e.target.files[0]; 55 | const reader = new FileReader(); 56 | reader.onload = ee => { 57 | const base64 = ee.target.result; 58 | 59 | if (_this.prop('tagName') === 'IMG') { 60 | // 如果是image标签 61 | _this.attr('src', base64); 62 | } else if (_this.css('background-image') !== 'none') { 63 | // 如果图片是通过背景图来显示的 64 | setBackgroundImage(_this, base64); 65 | } else { 66 | // 如果图片在某个子元素中(img/背景图) 67 | 68 | // 目标元素 69 | let target = false; 70 | // 类型 71 | let type = ''; 72 | // 遍历计数控制器 73 | let count = 0; 74 | // 深度优先遍历 75 | let stack = _this.children(); 76 | while (!target && stack.length && count < 100) { 77 | count++; 78 | const element = $(stack.splice(0, 1)); 79 | 80 | if (element.prop('tagName') === 'IMG' || _this.css('background-image') !== 'none') { 81 | target = element; 82 | type = element.prop('tagName') === 'IMG' ? 'IMG' : 'BGI'; 83 | break; 84 | } 85 | 86 | const children = element.children(); 87 | if (children.length) { 88 | $.merge(stack, children); 89 | } 90 | } 91 | 92 | if (type === 'IMG') { 93 | target.attr('src', base64); 94 | } else if (type === 'BGI') { 95 | setBackgroundImage(target, base64); 96 | } else { 97 | // console.error('没有找到图片所在元素,目标:', _this, 'count次数:', count); 98 | } 99 | } 100 | 101 | // 删除srcset属性,不管它存在不存在。 102 | // 知乎有这个东西 103 | _this.removeAttr('srcset'); 104 | 105 | fakescreenshot.hide(); 106 | }; 107 | reader.readAsDataURL(files); 108 | }; 109 | 110 | let fakescreenshot = $('.fakescreenshot-upload'); 111 | if (!fakescreenshot.length) { 112 | // 第一次调用时才塞入 113 | const dom = initDOM(options); 114 | $('body').prepend(dom); 115 | fakescreenshot = dom; 116 | 117 | fakescreenshot.hover(function() { 118 | $(this) 119 | .find('svg.close') 120 | .show(); 121 | }); 122 | fakescreenshot.mouseout(function(e) { 123 | if (e.toElement && !$(e.toElement).parents('.fakescreenshot-upload').length) { 124 | $(this) 125 | .find('svg.close') 126 | .hide(); 127 | } 128 | }); 129 | fakescreenshot.find('svg.close').click(function(e) { 130 | e.stopPropagation(); 131 | fakescreenshot.remove(); 132 | }); 133 | } else { 134 | // 重新设置position 135 | fakescreenshot.css('left', `${options.left}px`); 136 | fakescreenshot.css('top', `${options.top - 90}px`); 137 | fakescreenshot.show(); 138 | } 139 | 140 | fakescreenshot 141 | .find('input') 142 | .off('change') 143 | .change(inputChange); 144 | }; 145 | })(jQuery); 146 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FakeScreenshot", 3 | "description": "FakeScreenshot/虚假截图制作工具:截图 = 实锤?相信你就输了!", 4 | "version": null, 5 | "manifest_version": 2, 6 | "permissions": ["contextMenus", "tabs", "activeTab"], 7 | "icons": { 8 | "48": "icons/48.png", 9 | "128": "icons/128.png" 10 | }, 11 | "browser_action": { 12 | "default_title": "FakeScreenshot", 13 | "default_popup": "popup/popup.html" 14 | }, 15 | "background": { 16 | "scripts": ["background.js"] 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": ["http://*/*", "https://*/*"], 21 | "js": ["jquery.js", "jquery.upload.js", "page.js"] 22 | } 23 | ], 24 | "options_ui": { 25 | "page": "options/options.html", 26 | "chrome_style": true 27 | }, 28 | "web_accessible_resources": ["src/options/options.html"] 29 | } 30 | -------------------------------------------------------------------------------- /src/options/App.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 99 | 100 | 276 | -------------------------------------------------------------------------------- /src/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FakeScreenshot - 检测页 7 | 8 | <% if (NODE_ENV === 'development') { %> 9 | 10 | <% } %> 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/options/options.js: -------------------------------------------------------------------------------- 1 | global.browser = require('webextension-polyfill'); 2 | 3 | import Vue from 'vue'; 4 | import App from './App'; 5 | 6 | /* eslint-disable no-new */ 7 | new Vue({ 8 | el: '#app', 9 | render: h => h(App), 10 | }); 11 | -------------------------------------------------------------------------------- /src/page.js: -------------------------------------------------------------------------------- 1 | // 展示文本输入框 2 | const fetchTextArea = html => { 3 | return $(`
4 | 5 |
6 | ${html} 7 |
8 | 9 |
10 | `); 67 | }; 68 | 69 | const initTextarea = (targetDOM, html) => { 70 | let dom = $('.fakescreenshot-textarea'); 71 | 72 | if (!dom.length) { 73 | // 如果第一次添加此DOM 74 | dom = fetchTextArea(html); 75 | dom.find('.close').click(() => { 76 | dom.find('.textarea').empty(); 77 | dom.hide(); 78 | }); 79 | 80 | $('body').append(dom); 81 | } else { 82 | dom.find('.textarea').html(html); 83 | // TODO 动画效果 84 | dom.show(); 85 | } 86 | 87 | dom 88 | .find('.confirm') 89 | .off('click') 90 | .click(() => { 91 | const newHTML = dom.find('.textarea').html(); 92 | targetDOM.html(newHTML); 93 | dom.hide(); 94 | }); 95 | 96 | dom.find('.textarea').focus(); 97 | }; 98 | 99 | const parseStyle = obj => { 100 | let style = ''; 101 | for (let [key, value] of Object.entries(obj)) { 102 | style += `${key}:${value};`; 103 | } 104 | 105 | return style; 106 | }; 107 | 108 | // 打上项目水印 109 | const watermark = ({ opacity = 0.005, left, top, width, height, lineHeight, letterSpacing, fontSize = '14', fontColor, textLength, type }) => { 110 | // 水印在字体14px的情况下所占的面积。 111 | let text = 'Made by FakeScreenshot'; 112 | 113 | // 如果文字类型,那么大概估算需要的水印文字量。(尽量在视觉上占满原DOM) 114 | if (type === 1 || type === 2) { 115 | // 考虑到一个汉字宽度 = 两个字母宽度 116 | text = text.repeat((textLength / text.length) * 2); 117 | } else if (type === 3) { 118 | text = text.repeat(Math.floor((width * height) / 4000)); 119 | } 120 | 121 | console.log(fontSize, width, height, top, left); 122 | 123 | const style = { 124 | opacity: opacity, 125 | position: 'absolute', 126 | left: left + 'px', 127 | top: top + 5 + 'px', // 稍微偏移,方便识别 128 | 'pointer-events': 'none', 129 | color: fontColor, 130 | 'z-index': 999999, 131 | 'font-size': type === 3 ? '40px' : fontSize + 'px', 132 | width: width + 'px', 133 | height: height + 'px', 134 | 'line-height': lineHeight, 135 | 'letter-spacing': letterSpacing, 136 | 'word-break': 'break-all', 137 | 'font-weight': 'bold', 138 | }; 139 | 140 | const waterDOM = $(`
${text}
`); 141 | 142 | $('body').append(waterDOM); 143 | }; 144 | 145 | // 存放用户右键时的DOM 146 | let $tempDOM; 147 | // DOM的屏幕位置信息 148 | let offset = {}; 149 | 150 | window.oncontextmenu = e => { 151 | const target = e.target; 152 | $tempDOM = $(target); 153 | const temp = $tempDOM.offset(); 154 | offset.left = temp.left; 155 | offset.top = temp.top; 156 | offset.width = $tempDOM.width(); 157 | offset.height = $tempDOM.height(); 158 | offset.fontColor = $tempDOM.css('color'); 159 | offset.lineHeight = $tempDOM.css('line-height'); 160 | offset.letterSpacing = $tempDOM.css('letter-spacing'); 161 | offset.fontSize = $tempDOM.css('font-size').replace('px', ''); 162 | offset.textLength = $tempDOM.text().length; 163 | }; 164 | 165 | chrome.runtime.onMessage.addListener(function(request) { 166 | if (!$tempDOM || !$tempDOM.length) { 167 | alert('请将鼠标置于页面文字/图片上 —> 鼠标右键 —> FakeScreenshot'); 168 | return; 169 | } 170 | 171 | /** 172 | * 修改文字(就地) 173 | * 如果类型为1,并且没有修改过 174 | */ 175 | if (request.type === 1 && !$tempDOM.attr('contenteditable')) { 176 | // clone是为了去除元素原有的点击事件(比如点击就展开文本) 177 | const clone = $tempDOM.clone(); 178 | clone.attr('contenteditable', true); 179 | $tempDOM.replaceWith(clone); 180 | clone.focus(); 181 | watermark({ 182 | ...offset, 183 | type: request.type, 184 | }); 185 | } else if (request.type === 2) { 186 | // 修改文字(弹框) 187 | initTextarea($tempDOM, $tempDOM.html()); 188 | watermark({ 189 | ...offset, 190 | type: request.type, 191 | }); 192 | } else if (request.type === 3) { 193 | // 修改图片 194 | $tempDOM.image(offset); 195 | 196 | // 图片的水印视觉上可以看到 197 | watermark({ 198 | ...offset, 199 | type: request.type, 200 | opacity: 0.02, 201 | }); 202 | } 203 | 204 | // 置空,为了在用户右键扩展图标进入时做判断。 205 | $tempDOM = null; 206 | }); 207 | -------------------------------------------------------------------------------- /src/popup/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 8 | 13 | <% if (NODE_ENV === 'development') { %> 14 | 15 | <% } %> 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/popup/popup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App'; 3 | import router from './router'; 4 | 5 | global.browser = require('webextension-polyfill'); 6 | Vue.prototype.$browser = global.browser; 7 | 8 | /* eslint-disable no-new */ 9 | new Vue({ 10 | el: '#app', 11 | router, 12 | render: h => h(App), 13 | }); 14 | -------------------------------------------------------------------------------- /src/popup/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import routes from './routes'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | export default new VueRouter({ 8 | routes, 9 | }); 10 | -------------------------------------------------------------------------------- /src/popup/router/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 48 | 49 | 114 | -------------------------------------------------------------------------------- /src/popup/router/routes.js: -------------------------------------------------------------------------------- 1 | import PageIndex from './pages/Index'; 2 | 3 | export default [ 4 | { 5 | path: '/', 6 | component: PageIndex, 7 | }, 8 | ]; 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const ejs = require('ejs'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyPlugin = require('copy-webpack-plugin'); 5 | const ExtensionReloader = require('webpack-extension-reloader'); 6 | const { VueLoaderPlugin } = require('vue-loader'); 7 | const { version } = require('./package.json'); 8 | 9 | const config = { 10 | mode: process.env.NODE_ENV, 11 | context: __dirname + '/src', 12 | entry: { 13 | background: './background.js', 14 | page: './page.js', 15 | jquery: './jquery.js', 16 | 'jquery.upload': './jquery.upload.js', 17 | 'popup/popup': './popup/popup.js', 18 | 'options/options': './options/options.js', 19 | }, 20 | output: { 21 | path: __dirname + '/dist', 22 | filename: '[name].js', 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.vue'], 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.vue$/, 31 | loaders: 'vue-loader', 32 | }, 33 | { 34 | test: /\.js$/, 35 | loader: 'babel-loader', 36 | exclude: /node_modules/, 37 | }, 38 | { 39 | test: /\.css$/, 40 | use: [MiniCssExtractPlugin.loader, 'css-loader'], 41 | }, 42 | { 43 | test: /\.scss$/, 44 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 45 | }, 46 | { 47 | test: /\.sass$/, 48 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader?indentedSyntax'], 49 | }, 50 | { 51 | test: /\.(png|jpg|jpeg|gif|svg|ico)$/, 52 | loader: 'file-loader', 53 | options: { 54 | name: '[name].[ext]', 55 | outputPath: '/images/', 56 | emitFile: false, 57 | }, 58 | }, 59 | { 60 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 61 | loader: 'file-loader', 62 | options: { 63 | name: '[name].[ext]', 64 | outputPath: '/fonts/', 65 | emitFile: false, 66 | }, 67 | }, 68 | ], 69 | }, 70 | plugins: [ 71 | new webpack.DefinePlugin({ 72 | global: 'window', 73 | }), 74 | new VueLoaderPlugin(), 75 | new MiniCssExtractPlugin({ 76 | filename: '[name].css', 77 | }), 78 | new CopyPlugin([ 79 | { from: 'icons', to: 'icons', ignore: ['icon.xcf'] }, 80 | { from: 'popup/popup.html', to: 'popup/popup.html', transform: transformHtml }, 81 | { from: 'options/options.html', to: 'options/options.html', transform: transformHtml }, 82 | { 83 | from: 'manifest.json', 84 | to: 'manifest.json', 85 | transform: content => { 86 | const jsonContent = JSON.parse(content); 87 | jsonContent.version = version; 88 | 89 | if (config.mode === 'development') { 90 | jsonContent['content_security_policy'] = "script-src 'self' 'unsafe-eval'; object-src 'self'"; 91 | } 92 | 93 | return JSON.stringify(jsonContent, null, 2); 94 | }, 95 | }, 96 | ]), 97 | ], 98 | }; 99 | 100 | if (config.mode === 'production') { 101 | config.plugins = (config.plugins || []).concat([ 102 | new webpack.DefinePlugin({ 103 | 'process.env': { 104 | NODE_ENV: '"production"', 105 | }, 106 | }), 107 | ]); 108 | } 109 | 110 | if (process.env.HMR === 'true') { 111 | config.plugins = (config.plugins || []).concat([ 112 | new ExtensionReloader({ 113 | manifest: __dirname + '/src/manifest.json', 114 | }), 115 | ]); 116 | } 117 | 118 | function transformHtml(content) { 119 | return ejs.render(content.toString(), { 120 | ...process.env, 121 | }); 122 | } 123 | 124 | module.exports = config; 125 | --------------------------------------------------------------------------------