├── .gitignore ├── .jshintrc ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE.txt ├── Readme.md ├── _locales ├── de │ └── messages.json └── en │ └── messages.json ├── build.py ├── content-script.js ├── icon.png ├── icon.svg ├── icon32.png ├── icon64.png ├── main.js ├── manifest.json ├── options.html └── options.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.xpi 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "curly": true, 4 | "esnext": true, 5 | "freeze": true, 6 | "globalstrict": true, 7 | "moz": true, 8 | "noarg": true, 9 | "nonbsp": true, 10 | "strict": true, 11 | "undef": true, 12 | "unused": true, 13 | "validthis": true, 14 | "maxlen": 80, 15 | "predef": ["Components", "Cc", "Ci", "Cr", "Cu", "require", "log", 16 | "LOG_DEBUG", "LOG_ERROR", "LOG_INFO", "prefs", "lazyRequire", 17 | "lazy", "Services", "Instances", "XPCOMUtils", "exports", 18 | "reportError", "ADDON", "unload", "BASE_PATH", "Iterator", 19 | "globalPrefs", "weak"] 20 | } 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, thanks for considering contributing code or other resources. 4 | Much appreciated! 5 | 6 | ## Submitting issues 7 | 8 | Please try to keep your texts short, but still include enough detail for other 9 | people to understand and **reproduce** your issue. 10 | E.g. if a bug affects a particular version, mention the version number. It might 11 | be also helpful to talk a bit about your environment, like operating system and 12 | platform (x86, x86_64, arm). 13 | 14 | For bugs or feature requests provide: 15 | 16 | - Sensible, but short summary/title. 17 | - Description of the actual behavior, preferably with detailed steps to 18 | reproduce the issue. 19 | - Description of the expected behavior. 20 | - If you know about older versions that worked correctly, mention that as well. 21 | 22 | Be polite, even if your issue does not (initially) get a lot of attention, 23 | or project maintainers close it as invalid/wontfix/etc. 24 | 25 | If you already diagnosed an issue and found the root cause, and feel like 26 | spending some extra time coding up a fix, then please don't hesitate to file a 27 | pull request instead of an issue. Project maintainers will love you. 28 | 29 | ### Bad 30 | 31 | > There is a bug in your software. When I click the button, it crashes. 32 | 33 | ### Better 34 | 35 | > Crash in version 1.0 when clicking Print button 36 | > 37 | > Hey, I'm using your software in version 1.0 on a Windows 7 (64-bit) system 38 | > along with FooBar Browser version 13.0. Unfortunately, it crashes when I 39 | > click on the Print button. This didn't happen in version 0.9. 40 | > 41 | > 1. Open document xyz 42 | > 2. Click Print button 43 | > 3. The application becomes unresponsive and has to be killed manually. 44 | > 45 | > I expect the application to print the document and then to continue working 46 | > normally. 47 | 48 | ## Submitting code and/or documentation 49 | 50 | - Your code should look and feel like the other code in the project, e.g. it 51 | should try to mimic/abide by the existing code formatting style and API 52 | design decisions. 53 | - If you plan to implement or revise a major feature/major API, or 54 | *break things* (for the better) in general, then please file an issue or 55 | pull request early. That way the project maintainers might suggest changes 56 | early and/or refine/reject ideas before you spend a lot time writing code that 57 | won't be merged in the current form. 58 | - Please provide (unit) tests, where appropriate and feasible. 59 | - Please use pull requests and avoid *plain* patches, etc. 60 | - Your commits should include at the very least an exploratory in plain English. 61 | Feel free to make use additional long messages current version control systems 62 | support, in particular if your commit is anything more than a simple bugfix. 63 | - Try to use gender-neutral language. Why? For four good reasons: 64 | - It is more inclusive and should work well-enough for all people. 65 | - It doesn't cost you anything, really, and no, it does not infringe upon your 66 | rights. It is easy enough to do, at least, in the English language. 67 | - Most importantly: because this file tells you to ;) 68 | 69 | ## Copyrights and License 70 | 71 | Any new code you submit **must be licensed** under the same license as the 72 | project license. 73 | Exception to this rule are only third-party libraries, which have to be 74 | licensed under a compatible license nonetheless and may **not** make the whole 75 | project less permissive. 76 | E.g. you may **not** submit code that uses plain GPL in a derivative way in a 77 | project otherwise licensed under a more permissive license such as the 78 | BSD/MIT/GPL licenses. 79 | 80 | You retain your copyright, or may assign it to the project/project maintainer. 81 | However, you must of course own the copyright or have the permission from the 82 | owners before submitting code. Work-for-hire-laws can be complicated. 83 | 84 | ## Enforcement 85 | 86 | The points outlined in this document are more guidelines than 87 | rules-set-in-stone. 88 | 89 | If in doubt, **the project maintainer(s) make the final decision**. 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | See: http://www.mozilla.org/MPL/2.0/index.txt 5 | Or see: LICENSE file in the repository 6 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Re-Pagination 2 | === 3 | 4 | This add-on is an update of the old re-pagination/antipagination extension for Firefox 57+. 5 | Nils Maier has not assigned ownership over to this project, so this is still a fork. 6 | 7 | Features 8 | === 9 | 10 | * WebExtension compatible with Firefox Quantum 11 | * Compatible with more sites 12 | * All, limited and slideshow 13 | -------------------------------------------------------------------------------- /_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "displaySubmenu_label": { 3 | "message": "Untermenü anzeigen" 4 | }, 5 | "displaySubmenu_desc": { 6 | "message": "Wenn diese Option aktiviert ist (Standard), dann wird Re-Pagination als Untermenü angezeigt anstatt die verschiedenen Aktionen direkt im Kontextmenü anzuzeigen." 7 | }, 8 | "showslideshow_label": { 9 | "message": "'Slide-Show' Menüeintrag anzeigen" 10 | }, 11 | "showalldomain_label": { 12 | "message": "'Alle Tabs der aktuellen Domain' Menüeintrag anzeigen" 13 | }, 14 | "loglevel_label": { 15 | "message": "Protokoll Stufe" 16 | }, 17 | "loglevel_desc": { 18 | "message": "Die Nachrichten werden in der Fehlerkonsole protokolliert. Man sollte diese Einstellung auf 'Keine Protokollierung' belassen, wenn nicht anders angewiesen." 19 | }, 20 | "loglevel_none_label": { 21 | "message": "Keine Protokollierung" 22 | }, 23 | "loglevel_error_label": { 24 | "message": "Fehler protokollieren" 25 | }, 26 | "loglevel_info_label": { 27 | "message": "Fehler und Infos protokollieren" 28 | }, 29 | "loglevel_debug_label": { 30 | "message": "Alles protokollieren!" 31 | }, 32 | "menu_label": { 33 | "message": "Re-Pagination" 34 | }, 35 | "repagination_nolimit": { 36 | "message": "Alle laden" 37 | }, 38 | "repagination_nolimit_domain": { 39 | "message": "'Alle Tabs der aktuellen Domain'" 40 | }, 41 | "repagination_limit": { 42 | "message": "Begrenzt" 43 | }, 44 | "repagination_slide": { 45 | "message": "Slide-Show" 46 | }, 47 | "repagination_slide_0": { 48 | "message": "Sofort" 49 | }, 50 | "repagination_slide_1": { 51 | "message": "1 Sekunde" 52 | }, 53 | "repagination_slide_60": { 54 | "message": "1 Minute" 55 | }, 56 | "repagination_stop": { 57 | "message": "Stoppen!" 58 | }, 59 | "repagination_limit_x": { 60 | "message": "$1 Seiten" 61 | }, 62 | "repagination_slide_120": { 63 | "message": "2 Minuten" 64 | }, 65 | "repagination_slide_x": { 66 | "message": "$1 Sekunden" 67 | }, 68 | 69 | "repagination_limited": { 70 | "message": "($1 of $2) Re-Pagination arbeitet..." 71 | }, 72 | "repagination_unlimited": { 73 | "message": "($1) Re-Pagination arbeitet..." 74 | }, 75 | "repagination_running": { 76 | "message": "Re-Pagination arbeitet..." 77 | }, 78 | 79 | "extensionName": { 80 | "message": "Re-Pagination" 81 | }, 82 | "extensionDescription": { 83 | "message": "Alle fortlaufenden Seiten in einem einzigem Tab auf einmal laden" 84 | } 85 | } -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "displaySubmenu_label": { 3 | "message": "Display submenu" 4 | }, 5 | "displaySubmenu_desc": { 6 | "message": "When this option is enabled (default), then Re-Pagination will be displayed as a submenu instead of displaying the various actions directly within the context menu." 7 | }, 8 | "showslideshow_label": { 9 | "message": "Show 'Slideshow' menu item" 10 | }, 11 | "showalldomain_label": { 12 | "message": "Show 'All tabs for current domain' menu item" 13 | }, 14 | "loglevel_label": { 15 | "message": "Log level" 16 | }, 17 | "loglevel_desc": { 18 | "message": "The messages will be logged to the Error Console. You should leave this at 'No Logging' unless instructed otherwise" 19 | }, 20 | "loglevel_none_label": { 21 | "message": "No Logging" 22 | }, 23 | "loglevel_error_label": { 24 | "message": "Log Errors" 25 | }, 26 | "loglevel_info_label": { 27 | "message": "Log Errors and Infos" 28 | }, 29 | "loglevel_debug_label": { 30 | "message": "Log Everything!" 31 | }, 32 | "menu_label": { 33 | "message": "Re-Pagination" 34 | }, 35 | "repagination_nolimit": { 36 | "message": "Load all" 37 | }, 38 | "repagination_nolimit_domain": { 39 | "message": "All tabs for current domain" 40 | }, 41 | "repagination_limit": { 42 | "message": "Limited" 43 | }, 44 | "repagination_limit_x": { 45 | "message": "$1 pages" 46 | }, 47 | "repagination_slide": { 48 | "message": "Slideshow" 49 | }, 50 | "repagination_slide_0": { 51 | "message": "Immediately" 52 | }, 53 | "repagination_slide_1": { 54 | "message": "1 second" 55 | }, 56 | "repagination_slide_x": { 57 | "message": "$1 seconds" 58 | }, 59 | "repagination_slide_60": { 60 | "message": "1 minute" 61 | }, 62 | "repagination_slide_120": { 63 | "message": "2 minutes" 64 | }, 65 | "repagination_stop": { 66 | "message": "Stop!" 67 | }, 68 | "repagination_limited": { 69 | "message": "($1 of $2) Re-Pagination is running..." 70 | }, 71 | "repagination_unlimited": { 72 | "message": "($1) Re-Pagination is running..." 73 | }, 74 | "repagination_running": { 75 | "message": "Re-Pagination is running..." 76 | }, 77 | "extensionName": { 78 | "message": "Re-Pagination" 79 | }, 80 | "extensionDescription": { 81 | "message": "Load all consecutive pages in a single tab at once." 82 | } 83 | } -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | # vim: set nosmartindent et ts=4 sw=4 : 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | from contextlib import contextmanager as ctx 8 | from glob import glob 9 | 10 | from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED 11 | 12 | resources = [ 13 | "manifest.json", 14 | "_locales/*/messages.json", 15 | "icon.png", "icon32.png", "icon64.png", 16 | "LICENSE.txt", 17 | "*.js", "options.html" 18 | ] 19 | destination = "repagination.xpi" 20 | 21 | 22 | try: 23 | from xpisign.context import ZipFileMinorCompression as Minor 24 | except ImportError: 25 | from warnings import warn 26 | warn("No optimal compression available; install xpisign") 27 | 28 | @ctx 29 | def Minor(): 30 | yield 31 | 32 | 33 | def get_files(resources): 34 | for r in resources: 35 | if os.path.isfile(r): 36 | yield r 37 | continue 38 | for g in glob(r): 39 | yield g 40 | 41 | 42 | class ZipOutFile(ZipFile): 43 | def __init__(self, zfile): 44 | ZipFile.__init__(self, zfile, "w", ZIP_DEFLATED) 45 | 46 | def __enter__(self): 47 | return self 48 | 49 | def __exit__(self, type, value, traceback): 50 | self.close() 51 | 52 | 53 | # if os.path.exists(destination): 54 | # print >>sys.stderr, destination, "is in the way" 55 | # sys.exit(1) 56 | 57 | with Minor(), ZipOutFile(destination) as zp: 58 | for f in sorted(get_files(resources), key=str.lower): 59 | compress_type = ZIP_STORED if f.endswith(".png") else ZIP_DEFLATED 60 | zp.write(f, compress_type=compress_type) 61 | -------------------------------------------------------------------------------- /content-script.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | "use strict"; 5 | 6 | { 7 | console.log("Framescript loaded!"); 8 | let port = browser.runtime.connect(); 9 | 10 | var focusElement = null; 11 | 12 | let _ = function () { 13 | let args = Array.from(arguments).map(e => e.toString()); 14 | return browser.i18n.getMessage(args[0], args.slice(1)); 15 | } 16 | 17 | let equalLinks = (left, right) => 18 | left.pathname === right.pathname && 19 | left.search === right.search && 20 | left.host === right.host && 21 | left.protocol === right.protocol; 22 | 23 | let getFirstSnapshot = (doc, node, query) => 24 | doc.evaluate(query, node, null, 7, null).snapshotItem(0); 25 | 26 | let createPageRequest = (srcurl, allowScripts, loadFun) => { 27 | let errorCount = 0; 28 | function sendRequest() { 29 | var xhr = new XMLHttpRequest(); 30 | xhr.onload = function() { 31 | if (xhr.status == 200) { 32 | console.info("XHR loaded, going to process"); 33 | try { 34 | loadFun(xhr.responseXML); 35 | } 36 | catch (ex) { 37 | console.error("failed to invoke callback on XHR", ex); 38 | } 39 | } else if (++errorCount <= 5) { 40 | console.info("XHR err'ed out, retrying"); 41 | setTimeout(function(){ sendRequest(); }, 500 * (2**errorCount)); 42 | } else { 43 | console.error("XHR err'ed out, giving up"); 44 | } 45 | } 46 | xhr.open('GET', srcurl); 47 | xhr.responseType = "document"; 48 | xhr.send(); 49 | } 50 | sendRequest(); 51 | }; 52 | 53 | let Repaginator = function Repaginator(count, allowScripts, yielding) { 54 | this.pageLimit = count || 0; 55 | this.allowScripts = allowScripts; 56 | this.yielding = yielding; 57 | this.init(); 58 | }; 59 | Repaginator.prototype = { 60 | slideshow: false, 61 | pageLimit: 0, 62 | seconds: 0, 63 | pageCount: 0, 64 | attemptToIncrement: true, 65 | 66 | init: function R_init() { 67 | // find anchor 68 | (function findInitialAnchor() { 69 | for (let parent = focusElement; parent; parent = parent.parentNode) { 70 | if (parent.localName == "a") { 71 | focusElement = parent; 72 | return; 73 | } 74 | } 75 | throw new Error("No focus element"); 76 | })(); 77 | }, 78 | buildQuery: function R_buildQuery(el) { 79 | function escapeXStr(mystr) { 80 | return mystr.replace(/'/g, "\\'"); 81 | } 82 | 83 | this.query = ""; 84 | 85 | (function buildQuery() { 86 | // Homestuck hack 87 | if(el.href && el.href.includes("mspaintadventures.com")) { 88 | this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; 89 | this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; 90 | return; 91 | } 92 | 93 | // See if the anchor has an ID 94 | // Note: cannot use the id() xpath function here, as there might 95 | // be duplicate ids 96 | if (el.id) { 97 | this.query = "//a[@id='" + escapeXStr(el.id) + "']"; 98 | this.numberToken = /(\[@id='.*?)(\d+)(.*?'\])/; 99 | return; 100 | } 101 | 102 | // See if the document has a link rel="..." pointing to the same place 103 | let linkEl = getFirstSnapshot(el.ownerDocument, el.ownerDocument, 104 | "//head//link[@href='" + 105 | escapeXStr(el.href) + "']"); 106 | if (linkEl) { 107 | let rel = linkEl.getAttribute("rel") || ""; 108 | if (rel.trim()) { 109 | this.query = "/html/head//link[@rel='" + escapeXStr(rel) + "']"; 110 | // no point in checking for numbers 111 | this.attemptToIncrement = false; 112 | console.log("using link[@rel]"); 113 | return; 114 | } 115 | } 116 | 117 | // Find an id in the ancestor chain, or alternatively a class 118 | // that we may operate on 119 | (function findPathPrefix() { 120 | let pieces = []; 121 | for (let pn = el.parentNode; pn; pn = pn.parentNode) { 122 | if (pn.localName == "body") { 123 | break; 124 | } 125 | if (pn.id) { 126 | console.log("got id: " + pn.id); 127 | pieces.unshift("//" + pn.localName + "[@id='" + 128 | escapeXStr(pn.id) + "']"); 129 | break; // one id is enough 130 | } 131 | if (pn.className) { 132 | console.log("got class: " + pn.className); 133 | pieces.unshift("//" + pn.localName + "[@class='" + 134 | escapeXStr(pn.className) + "']"); 135 | break; 136 | } 137 | } 138 | this.query = pieces.join(""); 139 | console.log("findPathPrefix result: " + this.query); 140 | }).call(this); 141 | 142 | // find the anchor 143 | (function findAnchor() { 144 | let text = el.textContent; 145 | 146 | // See if there is rel=next or rel=prev 147 | let rel = (el.getAttribute("rel") || "").trim(); 148 | if (rel && (rel.includes("next") || rel.includes("prev"))) { 149 | this.query += "//a[@rel='" + escapeXStr(rel) + "']"; 150 | // no point in checking for numbers 151 | this.attemptToIncrement = false; 152 | console.log("using a[@rel]"); 153 | return; 154 | } 155 | 156 | // Try the node text 157 | if (text.trim()) { 158 | this.query += "//a[.='" + escapeXStr(text) + "']"; 159 | this.numberToken = /(a\[.='.*?)(\d+)(.*?\])/; 160 | console.log("using text"); 161 | return; 162 | } 163 | 164 | // See if it has a descendant with a @src we may use 165 | let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); 166 | if (srcEl) { 167 | let src = srcEl.getAttribute("src") || ""; 168 | if (src.trim()) { 169 | this.query += "//" + srcEl.localName + "[@src='" + escapeXStr(src) + 170 | "']/ancestor::a"; 171 | this.numberToken = /(\[@src='.*?)(\d+)(.*?'\])/; 172 | console.log("using @src"); 173 | return; 174 | } 175 | } 176 | 177 | // See if there is a descendant with a @value we may use 178 | srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); 179 | if (srcEl) { 180 | let val = srcEl.getAttribute("value") || ""; 181 | if (val.trim()) { 182 | this.query += "//" + srcEl.localName + "[@value='" + 183 | escapeXStr(val) + "']/ancestor::a"; 184 | this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; 185 | console.log("using @value"); 186 | return; 187 | } 188 | } 189 | 190 | // See if there is a class we may use 191 | if (el.className) { 192 | this.query += "//a[@class='" + escapeXStr(el.className) + "']"; 193 | this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; 194 | console.log("using a[@class]"); 195 | return; 196 | } 197 | 198 | // See if there is a descendant with an id we may use 199 | srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@id]"); 200 | if (srcEl) { 201 | let val = srcEl.getAttribute("id") || ""; 202 | if (val.trim()) { 203 | this.query += "//" + srcEl.localName + "[@id='" + 204 | escapeXStr(val) + "']/ancestor::a"; 205 | this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; 206 | console.log("using descendant class"); 207 | return; 208 | } 209 | } 210 | 211 | throw new Error("No anchor expression found!"); 212 | }).call(this); 213 | }).call(this); 214 | 215 | 216 | // We're after the last result 217 | this.query = "(" + this.query + ")[last()]"; 218 | console.info("query: " + this.query); 219 | }, 220 | setTitle: function R_setTitle() { 221 | if (this.pageLimit) { 222 | document.title = 223 | _("repagination_limited", this.pageCount, this.pageLimit); 224 | } 225 | else if (this.pageCount > 0) { 226 | document.title = _("repagination_unlimited", this.pageCount); 227 | } 228 | else { 229 | document.title = _("repagination_running"); 230 | } 231 | }, 232 | finished: function R_finished() { 233 | port.postMessage({msg: "finished"}); 234 | stop(); 235 | // restore title 236 | if("_title" in this) { 237 | document.title = this._title; 238 | delete this._title; 239 | } 240 | }, 241 | repaginate: function R_repaginate() { 242 | this._title = document.title; 243 | this.setTitle(); 244 | try { 245 | let node = document.evaluate( 246 | this.query, document, null, 9, null).singleNodeValue; 247 | if (!node) { 248 | throw new Error("no node"); 249 | } 250 | document.body.setAttribute("repagination", "true"); 251 | createPageRequest(node.href, this.allowScripts, frame => { 252 | this.loadNext(node.href, frame, 0); 253 | }); 254 | } catch (ex) { 255 | this.finished(); 256 | console.error("repaginate failed", ex); 257 | } 258 | }, 259 | incrementQuery: function R_incrementQuery() { 260 | return this.query.replace(this.numberToken, function(g, pre, num, post) { 261 | return pre + (parseInt(num, 10) + 1) + post; 262 | }); 263 | }, 264 | loadNext: function R_loadNext(src, element, delay) { 265 | if (delay > 0) { 266 | console.log("delaying (sto) " + delay); 267 | setTimeout(() => this.loadNext(src, element, 0), delay); 268 | return; 269 | } 270 | 271 | try { 272 | this._loadNext_gen.bind(this, src, element)(); 273 | } catch (ex) { 274 | console.error("failed to process loadNext (non-yielding)", ex); 275 | this.finished(); 276 | } 277 | return; 278 | }, 279 | _loadNext_gen: function R__loadNext_gen(src, element) { 280 | let ownerDoc = document; 281 | 282 | try { 283 | if (!ownerDoc.body.hasAttribute("repagination")) { 284 | throw new Error("not running"); 285 | } 286 | 287 | var doc = element; 288 | this.pageCount++; 289 | 290 | // Remove scripts from frame 291 | // The scripts should already be present in the parent (first page) 292 | // Duplicate scripts would cause more havoc (performance-wise) than 293 | // behaviour failures due to missing scripts 294 | // Note: This is NOT a security mechanism, but a performance thing. 295 | doc.querySelectorAll("script").forEach(s => s.parentNode.removeChild(s)); 296 | doc.querySelectorAll("style").forEach(s => s.parentNode.removeChild(s)); 297 | 298 | // Do the dirty deed 299 | if (this.slideshow) { 300 | console.info("replacing content (slideshow)"); 301 | // this should be safe since innerHTML returns serialized HTML 302 | ownerDoc.body.innerHTML = doc.body.innerHTML; 303 | ownerDoc.body.setAttribute("repagination", "true"); 304 | } 305 | else { 306 | console.info("inserting content (normal)"); 307 | // Remove non-same-origin iframes, such as ad iframes 308 | // Otherwise we might create a shitload of (nearly) identical frames 309 | // which might even kill the browser 310 | if (!this.pageLimit || this.pageLimit > 10) { 311 | console.info("removing non-same-origin iframes to avoid dupes"); 312 | let host = ownerDoc.defaultView.location.hostName; 313 | doc.querySelectorAll("iframe").forEach(function(f) { 314 | var url = new URL(f.src, ownerDoc.defaultView.location.href); 315 | if (url.hostname != host) { 316 | f.parentNode.removeChild(f); 317 | } 318 | }); 319 | } 320 | for (let c = doc.body.firstChild; c; c = c.nextSibling) { 321 | ownerDoc.body.appendChild(ownerDoc.importNode(c, true)); 322 | } 323 | } 324 | 325 | if (this.pageLimit && this.pageCount >= this.pageLimit) { 326 | throw new Error("done"); 327 | } 328 | 329 | let savedQuery; 330 | if (this.attemptToIncrement) { 331 | console.log("attempting to increment query"); 332 | let nq = this.incrementQuery(); 333 | if (nq == this.query) { 334 | console.log("query did not increment"); 335 | this.attemptToIncrement = false; 336 | } 337 | else { 338 | console.log("query did increment"); 339 | savedQuery = this.query; 340 | this.query = nq; 341 | } 342 | } 343 | let node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; 344 | let loc = src || null; 345 | if (this.attemptToIncrement && (!node || node.href == loc)) { 346 | console.log("no result after incrementing; restoring"); 347 | console.log("inc:" + this.query + " orig:" + savedQuery); 348 | this.query = savedQuery; 349 | node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; 350 | this.attemptToIncrement = false; 351 | } 352 | if (!node) { 353 | throw new Error("no next node found for query: " + this.query); 354 | } 355 | let nexturl = node.href.toString(); 356 | if (loc && loc == nexturl) { 357 | throw new Error("location did not change for query" + this.query); 358 | } 359 | if (equalLinks(node,window.location)) { 360 | throw new Error("loop back to first item"); 361 | } 362 | 363 | this.setTitle(); 364 | console.info("next please: " + nexturl); 365 | createPageRequest(nexturl, this.allowScripts, frame => { 366 | if (this.slideshow && this.seconds) { 367 | console.info("slideshow; delay: " + this.seconds * 1000); 368 | this.loadNext(nexturl, frame, this.seconds * 1000); 369 | } 370 | else { 371 | console.info("regular; no-delay"); 372 | this.loadNext(nexturl, frame, 0); 373 | } 374 | }); 375 | } 376 | catch (ex) { 377 | console.log(ex); 378 | console.info("loadNext complete"); 379 | this.finished(); 380 | } 381 | } 382 | }; 383 | Object.seal(Repaginator.prototype); 384 | 385 | let Slideshow = function Slideshow(seconds, allowScripts, yielding) { 386 | this.seconds = seconds || 0; 387 | this.slideshow = true; 388 | this.allowScripts = allowScripts; 389 | this.yielding = yielding; 390 | this.init(); 391 | }; 392 | Slideshow.prototype = Repaginator.prototype; 393 | 394 | let repaginate = (target, num, slideshow, allowScripts, yielding) => { 395 | try { 396 | let Ctor = slideshow ? Slideshow : Repaginator; 397 | let rep; 398 | focusElement = browser.menus.getTargetElement(target); 399 | rep = new Ctor(num, allowScripts, yielding); 400 | rep.buildQuery(focusElement); 401 | rep.repaginate(); 402 | } catch (ex) { 403 | console.error("Failed to run repaginate", ex); 404 | } 405 | }; 406 | 407 | let stop = () => { 408 | try { 409 | let body = document.body; 410 | if (body) { 411 | body.removeAttribute("repagination"); 412 | } 413 | } catch (ex) { 414 | console.error("failed to stop repagination", ex); 415 | } 416 | }; 417 | 418 | port.onMessage.addListener(msg => { 419 | switch (msg.msg) { 420 | case "normal": repaginate(msg.target, msg.num, msg.slideshow, msg.allowScripts, msg.yielding); break; 421 | case "stop" : stop(); break; 422 | } 423 | }); 424 | 425 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1370368 426 | window.addEventListener('pagehide', function(event) { 427 | stop(); 428 | try { 429 | port.disconnect(); 430 | } catch (ex) { 431 | console.log(ex) 432 | } 433 | }); 434 | 435 | } 436 | /* vim: set et ts=2 sw=2 : */ 437 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mathnerd314/repagination/b0214aa1e6b5d4b7df1b546a6b82db91a848ae8b/icon.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 90 | 92 | 93 | 94 | 96 | 97 | 99 | 100 | 103 | 104 | 106 | 107 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mathnerd314/repagination/b0214aa1e6b5d4b7df1b546a6b82db91a848ae8b/icon32.png -------------------------------------------------------------------------------- /icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mathnerd314/repagination/b0214aa1e6b5d4b7df1b546a6b82db91a848ae8b/icon64.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | "use strict"; 5 | 6 | const PORTS = {}; 7 | const PENDING = {}; 8 | const RUNNING = new Set(); 9 | 10 | function onError(error) { 11 | console.error(error); 12 | } 13 | 14 | const MENU = { 15 | NOLIMIT: 'REPAGINATION_NOLIMIT', 16 | LIMIT: 'REPAGINATION_LIMIT', 17 | SLIDE: 'REPAGINATION_SLIDE', 18 | STOP: 'REPAGINATION_STOP' 19 | } 20 | 21 | function onCreated(n) { 22 | if (browser.runtime.lastError) { 23 | console.error("Error creating menu item: %o", browser.runtime.lastError); 24 | } 25 | } 26 | 27 | function createMenu(prefs) { 28 | let c = ["link"]; // ContextTypes for most menu items 29 | 30 | browser.menus.create({ 31 | id: MENU.NOLIMIT, 32 | title: browser.i18n.getMessage("repagination_nolimit"), 33 | contexts: c 34 | }, onCreated); 35 | 36 | let limits = [5,10,25,50,100]; 37 | 38 | for(let j in limits) { 39 | let i = limits[j]; 40 | browser.menus.create({ 41 | id: MENU.LIMIT + "_" + i, 42 | title: browser.i18n.getMessage("repagination_limit_x",[i]), 43 | contexts: c 44 | }, onCreated); 45 | 46 | } 47 | 48 | if(prefs.slideshows) { 49 | browser.menus.create({ 50 | id: MENU.SLIDE, 51 | title: browser.i18n.getMessage("repagination_slide"), 52 | contexts: c 53 | }, onCreated); 54 | 55 | let slides = [0,1,2,4,5,10,15,30,60,120]; 56 | 57 | for(let j in slides) { 58 | let i = slides[j]; 59 | browser.menus.create({ 60 | id: MENU.SLIDE + "_" + i, 61 | parentId: MENU.SLIDE, 62 | title: ([0,1,60,120].indexOf(i) != -1) ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",[i]), 63 | contexts: c 64 | }, onCreated); 65 | } 66 | } 67 | 68 | updStop(); 69 | } 70 | 71 | var added = false; 72 | 73 | function updStop() { 74 | if(RUNNING.size > 0 && !added) { 75 | browser.menus.create({ 76 | id: MENU.STOP, 77 | title: browser.i18n.getMessage("repagination_stop"), 78 | contexts: ["all"] 79 | }, onCreated); 80 | added = true; 81 | } else if(added) { 82 | browser.menus.remove(MENU.STOP); 83 | added = false; 84 | } 85 | } 86 | 87 | var defaultSettings = { 88 | slideshows: false, 89 | exists: true // special pref to restore defaults 90 | }; 91 | 92 | function prefReset(newSettings, areaName) { 93 | console.log("prefs changed") 94 | if (areaName == "local" && ("exists" in newSettings)) { 95 | browser.menus.removeAll(); 96 | console.log("recreating menu") 97 | browser.storage.local.get().then(initSettings, onError); 98 | } 99 | } 100 | 101 | function initSettings(prefs) { 102 | if (!("exists" in prefs) || !prefs.exists) { 103 | browser.storage.onChanged.removeListener(prefReset); 104 | browser.storage.local.set(defaultSettings); 105 | browser.storage.onChanged.addListener(prefReset); 106 | prefs = defaultSettings; 107 | } 108 | 109 | createMenu(prefs); 110 | } 111 | 112 | function myinit(prefs) { 113 | initSettings(prefs); 114 | 115 | function process(info, tab) { 116 | let str = info.menuItemId; 117 | 118 | if(str == MENU.STOP) { 119 | console.info("stop"); 120 | PORTS[tab].postMessage({ 121 | msg: "stop" 122 | }); 123 | return; 124 | } 125 | 126 | let num, slideshow; 127 | if(str == MENU.NOLIMIT) { 128 | num = 0; 129 | slideshow = false; 130 | } else if(str.startsWith(MENU.LIMIT)) { 131 | // https://stackoverflow.com/questions/5555518/split-variable-from-last-slash-jquery 132 | num = parseInt(str.substring(str.lastIndexOf("_") + 1, str.length), 10); 133 | slideshow = false; 134 | } else if(str.startsWith(MENU.SLIDE)) { 135 | num = parseInt(str.substring(str.lastIndexOf("_") + 1, str.length), 10); 136 | slideshow = true; 137 | } 138 | 139 | console.info("repaginate: " + num + "/" + slideshow); 140 | try { 141 | PORTS[tab].postMessage({ 142 | msg: "normal", 143 | target: info.targetElementId, 144 | num: num, 145 | slideshow: slideshow, 146 | allowScripts: prefs.allowScripts, 147 | yielding: prefs.yielding 148 | }); 149 | RUNNING.add(tab); 150 | updStop(); 151 | } catch (ex) { 152 | console.log(ex); 153 | console.error("failed to run repaginate"); 154 | delete PORTS[tabid]; 155 | delete PENDING[tabid]; 156 | RUNNING.delete(tabid); 157 | } 158 | } 159 | 160 | // We lazily inject the main content script in a vague hope for efficiency 161 | // We use ports for messaging but have to store the messages until the port is opened. 162 | 163 | browser.menus.onClicked.addListener((info, tab) => { 164 | console.log(info, tab); 165 | if(tab.id in PORTS) { 166 | process(info, tab.id); 167 | } else { 168 | console.log("injecting " + tab.id); 169 | browser.tabs.executeScript(tab.id, { file: "content-script.js" } ); 170 | PENDING[tab.id] = info; 171 | } 172 | }); 173 | 174 | 175 | browser.runtime.onConnect.addListener(function(port) { 176 | let tabid = port.sender.tab.id; 177 | PORTS[tabid] = port; 178 | port.onDisconnect.addListener((p) => { 179 | delete PORTS[tabid]; 180 | delete PENDING[tabid]; 181 | RUNNING.delete(tabid); 182 | updStop(); 183 | }); 184 | port.onMessage.addListener(msg => { 185 | switch (msg.msg) { 186 | case "finished": 187 | RUNNING.delete(tabid); 188 | updStop(); 189 | break; 190 | } 191 | }); 192 | 193 | if (port.sender.tab.id in PENDING) { 194 | var info = PENDING[port.sender.tab.id]; 195 | delete PENDING[port.sender.tab.id]; 196 | process(info, port.sender.tab.id); 197 | } 198 | }); 199 | } 200 | 201 | browser.storage.local.get().then(myinit, onError); 202 | 203 | browser.storage.onChanged.addListener(prefReset); 204 | 205 | console.info("all good!"); 206 | 207 | /* vim: set et ts=2 sw=2 : */ 208 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_extensionName__", 4 | "version": "2020.5.27", 5 | "description": "__MSG_extensionDescription__", 6 | "homepage_url": "https://github.com/Mathnerd314/repagination/", 7 | 8 | "default_locale": "en", 9 | "options_ui": { 10 | "page": "options.html", 11 | "browser_style": true 12 | }, 13 | "icons": { "32": "icon32.png", 14 | "48": "icon.png", 15 | "64": "icon64.png"}, 16 | 17 | "applications": { 18 | "gecko": { 19 | "id": "repagination-fork-mathnerd314@github.com", 20 | "strict_min_version": "63.0" 21 | } 22 | }, 23 | 24 | "background": { 25 | "scripts": ["main.js"] 26 | }, 27 | 28 | "permissions": [ 29 | "activeTab", 30 | "storage", 31 | "menus", 32 | "" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 36 | 37 | 38 | 41 |
42 | 43 |
44 | 45 | 46 |
47 |

Credits

48 |
53 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | /* see also main.js */ 2 | 3 | /* 4 | Update the options UI with the settings values retrieved from storage, 5 | or the default settings if the stored settings are empty. 6 | */ 7 | function updateUI(restoredSettings) { 8 | document.getElementById("slideshows").checked = restoredSettings.slideshows || false; 9 | } 10 | 11 | function onError(e) { 12 | console.error(e); 13 | } 14 | 15 | browser.storage.local.get().then(updateUI, onError); 16 | 17 | document.getElementById("slideshows").onchange = function setSlideshows() { 18 | browser.storage.local.set({ exists: true, slideshows: document.getElementById("slideshows").checked}); 19 | }; 20 | --------------------------------------------------------------------------------