From 5bb6cc7830ab5f3553fce84665c21911aff93db4 Mon Sep 17 00:00:00 2001 From: Valerii Liasotskyi Date: Thu, 29 Feb 2024 15:52:13 +0100 Subject: [PATCH] fix: avoid using deprecated jQuery functions to prepare for jQuery 4 (#779) - some DOM check functions were rewritten to use native DOM API - jquery 3 dependency was removed from package.json - updated some function signatures in type definitions --- .gitignore | 1 + CHANGELOG.md | 1 + package-lock.json | 22 +--------- package.json | 4 -- src/gmail.d.ts | 29 +++++++------ src/gmail.js | 101 +++++++++++++++++++++---------------------- test/test.locale.js | 2 +- test/test.new.js | 2 +- test/test.parsing.js | 2 +- test/test.tools.js | 2 +- 10 files changed, 73 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index 2aac073f..a794b5d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.idea node_modules **/*~ **/.#* diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b32f2d4..219415c3 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Version 1.1.13 +- Drop bundled jQuery, support jQuery 4, support explicit no-jQuery mode - Fix reply button selector to support Gmail in text labels mode ## Version 1.1.12 diff --git a/package-lock.json b/package-lock.json index 282b20a6..02736f91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,16 @@ { "name": "gmail-js", - "version": "1.1.11", + "version": "1.1.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gmail-js", - "version": "1.1.11", + "version": "1.1.12", "license": "MIT", - "dependencies": { - "jquery": "^3.6.1" - }, "devDependencies": { "@types/jquery": "^3.5.14", "eslint": "^8.23.1", - "gmail-js": "^1.1.0", "jest": "^29.5.0", "jest-junit": "^16.0.0", "jsdom": "^20.0.0", @@ -2657,15 +2653,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gmail-js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gmail-js/-/gmail-js-1.1.0.tgz", - "integrity": "sha512-1RvO0xi4oKY2ihtVRzoLYvBgQDQXOSvVarDMF1Er/8XNYtouvGa803TloPrShT4qDvDqTGSoSaOiKxLxizFhdw==", - "dev": true, - "dependencies": { - "jquery": "^3.3.1" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3596,11 +3583,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jquery": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", - "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" - }, "node_modules/js-sdsl": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", diff --git a/package.json b/package.json index 754a8709..a50f1e3a 100755 --- a/package.json +++ b/package.json @@ -22,13 +22,9 @@ "gmail chrome extension", "gmail firefox extension" ], - "dependencies": { - "jquery": "^3.6.1" - }, "devDependencies": { "@types/jquery": "^3.5.14", "eslint": "^8.23.1", - "gmail-js": "^1.1.0", "jest": "^29.5.0", "jest-junit": "^16.0.0", "jsdom": "^20.0.0", diff --git a/src/gmail.d.ts b/src/gmail.d.ts index 0d92e82c..de6d5fce 100644 --- a/src/gmail.d.ts +++ b/src/gmail.d.ts @@ -23,7 +23,8 @@ declare type StringDict = { // //////////////////////////////////////////////////////////////////////////////// -interface GmailTracker { +interface GmailTracker { + dom_observers: { [observer in GmailDomObserver | T]?: DomObserverConfig }; globals: any[]; view_data: any[]; ik: string; @@ -31,10 +32,10 @@ interface GmailTracker { events: {}[]; actions: {}[]; watchdog: { - before: {}, - on: {}, - after: {}, - dom: {} + before: { [action in GmailBindAction | T]?: Function[] }; + on: { [action in GmailBindAction | T]?: Function[] }; + after: { [action in GmailBindAction | T]?: Function[] }; + dom: { [observer in GmailDomObserver | T]?: Function[] }; }; } @@ -671,7 +672,7 @@ interface GmailTools { observes every element inserted into the DOM by Gmail and looks at the classes on those elements, checking for any configured observers related to those classes */ - insertion_observer(target: HTMLElement | string, dom_observers: any, dom_observer_map: any, sub: any): void; + insertion_observer(target: HTMLElement | string, dom_observers: { [observer: string]: DomObserverConfig }, dom_observer_map: { [className: string]: string[] }, sub?: string): void; make_request(link: string, method: GmailHttpRequestMethod, disable_cache: boolean): string; make_request_async(link: string, method: GmailHttpRequestMethod, callback: (data: string) => void, disable_cache: boolean): void; @@ -764,6 +765,8 @@ declare type GmailBindAction = | 'new_email' | 'refresh' | 'open_email' | 'upload_attachment' | 'compose' | 'compose_cancelled' | 'recipient_change' | 'view_thread' | 'view_email' | 'load_email_menu'; +declare type GmailDomObserver = + 'view_thread' | 'view_email' | 'load_email_menu' | 'recipient_change' | 'compose' interface HttpEventRequestParams { url: object, @@ -780,7 +783,7 @@ interface DomObserverConfig { handler?: Function; } -interface GmailObserve { +interface GmailObserve { /** After an observer has been bound through gmail.observe.bind() (via a call to events gmail.observe.before(), gmail.observe.on(), or @@ -798,7 +801,7 @@ interface GmailObserve { /** Bind a specified callback to an array of callbacks against a specified type & action */ - bind(type: GmailBindType, action: Function, callback: Function): void; + bind(type: GmailBindType, action: GmailBindAction | T, callback: Function): void; /** an on event is observed just after gmail sends an xhr request @@ -850,11 +853,11 @@ interface GmailObserve { Trigger any specified events bound to the passed type Returns true or false depending if any events were fired */ - trigger(type: GmailBindType, events: any, xhr: XMLHttpRequest): boolean; + trigger(type: GmailBindType, events: { [action in GmailBindAction | T]?: any[] }, xhr: XMLHttpRequest): boolean; /** Trigger any specified DOM events passing a specified element & optional handler */ - trigger_dom(observer: any, element: HTMLElement, handler?: Function): void; + trigger_dom(observer: GmailDomObserver | T, element: HTMLElement, handler?: Function): void; initialize_dom_observers(): void; @@ -867,7 +870,7 @@ interface GmailObserve { className / args - for a simple observer, this arg can simply be the class on an inserted DOM element that identifies this event should be triggered. For a more complicated observer, this can be an object containing properties for each of the supported DOM observer config arguments */ - register(action: string, args: string | DomObserverConfig): void; + register(action: T, args: string | DomObserverConfig): void; /** Observe DOM nodes being inserted. When a node with a class defined in api.tracker.dom_observers is inserted, trigger the related event and fire off any relevant bound callbacks @@ -1031,14 +1034,14 @@ interface GmailCache { //////////////////////////////////////////////////////////////////////////////// declare class Gmail { - constructor(localJQuery?: JQueryStatic); + constructor(localJQuery?: JQueryStatic | false); version: string; /** These are some of the variables that are tracked and kept in memory while the rest of the methods are in use. */ - tracker: GmailTracker; + tracker: GmailTracker; get: GmailGet; check: GmailCheck; /** diff --git a/src/gmail.js b/src/gmail.js index 43180fea..06249799 100644 --- a/src/gmail.js +++ b/src/gmail.js @@ -13,18 +13,14 @@ var Gmail = function(localJQuery) { other extensions that use $ for other purposes. */ var $; - if (typeof localJQuery !== "undefined") { + if (localJQuery === false) { + // leave $ undefined, which may be fine for some purposes. + } else if (typeof localJQuery !== "undefined") { $ = localJQuery; } else if (typeof jQuery !== "undefined") { $ = jQuery; } else { - // try load jQuery through node. - try { - $ = require("jquery"); - } - catch(err) { - // else leave $ undefined, which may be fine for some purposes. - } + throw new Error("GmailJS requires jQuery to be present in global scope or provided as a constructor argument."); } var window_opener = typeof (window) !== "undefined" ? window.opener : null; @@ -42,6 +38,7 @@ var Gmail = function(localJQuery) { } } + /** @type Gmail */ var api = { get : {}, observe : {}, @@ -341,12 +338,12 @@ var Gmail = function(localJQuery) { api.check.is_tabbed_inbox = function() { - return $(".aKh").length === 1; + return document.querySelectorAll(".aKh").length === 1; }; api.check.is_right_side_chat = function() { - var chat = $(".ApVoH"); + var chat = document.querySelectorAll(".ApVoH"); if(chat.length === 0) { return false; } @@ -429,7 +426,7 @@ var Gmail = function(localJQuery) { return false; } - var items = $(".ii.gt .a3s"); + var items = document.querySelectorAll(".ii.gt .a3s"); var ids = []; for(var i=0; i 0; + return document.querySelector(".qh") !== null; }; api.check.is_rapportive_installed = function() { - return $("#rapportive-sidebar").length === 1; + return document.querySelector("#rapportive-sidebar") !== null; }; api.check.is_streak_installed = function() { - return $("[id^='bentoBox'],[id*=' bentoBox'],[class*=' bentoBox'],[class*='bentoBox']").length > 0; + return document.querySelector("[id^='bentoBox'],[id*=' bentoBox'],[class*=' bentoBox'],[class*='bentoBox']") !== null; }; api.check.is_anydo_installed = function() { - return $("[id^='anydo'],[id*=' anydo'],[class*=' anydo'],[class*='anydo']").length > 0; + return document.querySelector("[id^='anydo'],[id*=' anydo'],[class*=' anydo'],[class*='anydo']") !== null; }; api.check.is_boomerang_installed = function() { - return $("[id^='b4g_'],[id*=' b4g_'],[class*=' b4g_'],[class*='b4g_']").length > 0; + return document.querySelector("[id^='b4g_'],[id*=' b4g_'],[class*=' b4g_'],[class*='b4g_']") !== null; }; api.check.is_xobni_installed = function() { - return $("#xobni_frame").length > 0; + return document.querySelector("#xobni_frame") !== null; }; api.check.is_signal_installed = function() { - return $("[id^='Signal'],[id*=' Signal'],[class*=' signal'],[class*='signal']").length > 0; + return document.querySelector("[id^='Signal'],[id*=' Signal'],[class*=' signal'],[class*='signal']") !== null; }; @@ -636,9 +633,9 @@ var Gmail = function(localJQuery) { api.helper.get.navigation_count = function(i18nName) { const title = api.tools.i18n(i18nName); - const dom = $("div[role=navigation]").find("[title*='" + title + "']"); + const dom = document.querySelectorAll("div[role=navigation] [title*='" + title + "']"); - if (dom || dom.length > 0) { + if (dom.length > 0) { // this check should implicitly always be true, but better safe than sorry? if(dom[0].title.indexOf(title) !== -1) { const value = parseInt(dom[0].attributes['aria-label'].value.replace(/[^0-9]/g, "")); @@ -654,7 +651,7 @@ var Gmail = function(localJQuery) { api.get.beta = function() { var features = { - "new_nav_bar" : $("#gbz").length === 0 + "new_nav_bar" : document.querySelector("#gbz") !== null }; return features; @@ -2353,9 +2350,9 @@ var Gmail = function(localJQuery) { // if an object of actions (triggered events of format { event: [response] }) is passed, check if any of these are bound if(typeof action === "object") { var match = false; - $.each(action,function(key,response){ + for (let key of Object.keys(action)) { if(typeof api.tracker.watchdog[type][key] === "object") match = true; - }); + } return match; } if(type) return typeof api.tracker.watchdog[type][action] === "object"; @@ -2378,7 +2375,7 @@ var Gmail = function(localJQuery) { // loop through applicable types var types = type ? [ type ] : [ "before", "on", "after", "dom" ]; - $.each(types, function(idx, type) { + for (let type of types) { if(typeof api.tracker.watchdog[type] !== "object") return true; // no callbacks for this type // if action specified, remove any callbacks for this action, otherwise remove all callbacks for all actions @@ -2389,15 +2386,15 @@ var Gmail = function(localJQuery) { delete api.tracker.watchdog[type][action]; } } else { - $.each(api.tracker.watchdog[type], function(act,callbacks) { + for (let act of Object.keys(api.tracker.watchdog[type])) { if(typeof api.tracker.watchdog[type][act] === "object") { api.tracker.bound[act] -= api.tracker.watchdog[type][act].length; api.tracker.bound[type] -= api.tracker.watchdog[type][act].length; delete api.tracker.watchdog[type][act]; } - }); + } } - }); + } }; /** @@ -2407,19 +2404,19 @@ var Gmail = function(localJQuery) { api.observe.trigger = function(type, events, xhr) { if(!events) return false; var fired = false; - $.each(events, function(action,response) { + for (let [action, response] of Object.entries(events)) { // we have to do this here each time to keep backwards compatibility with old response_callback implementation - response = $.extend([], response); // break the reference so it doesn"t keep growing each trigger + response = [...response]; // break the reference so it doesn"t keep growing each trigger if(type === "after") response.push(xhr.xhrResponse); // backwards compat for after events requires we push onreadystatechange parsed response first response.push(xhr); if(api.observe.bound(action, type)) { fired = true; - $.each(api.tracker.watchdog[type][action], function(idx, callback) { + for (let callback of api.tracker.watchdog[type][action]) { callback.apply(undefined, response); - }); + } } - }); + } return fired; }; @@ -2437,9 +2434,9 @@ var Gmail = function(localJQuery) { if (!api.tracker.watchdog.dom[observer]) { return; } - $.each(api.tracker.watchdog.dom[observer], function(idx, callback) { + for (let callback of api.tracker.watchdog.dom[observer]) { handler(element, callback); - }); + } }; // pre-configured DOM observers @@ -2573,15 +2570,15 @@ var Gmail = function(localJQuery) { // map observed classNames to actions api.tracker.dom_observer_map = {}; - $.each(api.tracker.dom_observers, function(act,config){ - if(!$.isArray(config.class)) config.class = [config.class]; - $.each(config.class, function(idx, className) { + for (let [act, config] of Object.entries(api.tracker.dom_observers)) { + if (!Array.isArray(config.class)) config.class = [config.class]; + for (let className of config.class) { if (!api.tracker.dom_observer_map[className]) { api.tracker.dom_observer_map[className] = []; } api.tracker.dom_observer_map[className].push(act); - }); - }); + } + } //console.log( "observer_config", api.tracker.dom_observers, "dom_observer_map", api.tracker.dom_observer_map); }; @@ -2607,14 +2604,14 @@ var Gmail = function(localJQuery) { // was an object of arguments passed, or just a className var config = {}; - if (typeof args === "object" && !$.isArray(args)) { + if (typeof args === "object" && !Array.isArray(args)) { // copy over supported config - $.each(["class","selector","sub_selector","handler"], function(idx, arg) { + for (let arg of ["class", "selector", "sub_selector", "handler"]) { if(args[arg]) { config[arg] = args[arg]; } - }); + } } else { config["class"] = args; } @@ -2663,7 +2660,7 @@ var Gmail = function(localJQuery) { } // support for DOM observers - if($.inArray(action, api.tracker.supported_observers) > -1) { + if (api.tracker.supported_observers.includes(action)) { //console.log("observer found",api.tracker.dom_observers[action]); @@ -2754,7 +2751,7 @@ var Gmail = function(localJQuery) { var cn = target.className || ""; var classes = cn.trim ? cn.trim().split(/\s+/) : []; if(!classes.length) classes.push(""); // if no class, then check for anything observing nodes with no class - $.each(classes, function(idx, className) { + for (let className of classes) { var observers = dom_observer_map[className]; if (!observers) { return; @@ -2788,7 +2785,7 @@ var Gmail = function(localJQuery) { } } } - }); + } }; @@ -4347,17 +4344,17 @@ var Gmail = function(localJQuery) { // if update data has been passeed, loop through & create a new to_wrapper contents if (to_array) { - if (!$.isArray(to_array)) { + if (!Array.isArray(to_array)) { to_array = [to_array]; } var html = []; - $.each(to_array, function(index, obj) { + for (let obj in to_array) { html.push( $("").attr({ dir: "ltr", email: obj.email, name: obj.name }).addClass("g2").html(obj.name).wrap("

").parent().html()); - }); + } this.dom("to_wrapper").html("to " + html.join(", ")); } @@ -4424,9 +4421,9 @@ var Gmail = function(localJQuery) { // retrieve & cache the data for this whole thread of emails var data = api.get.email_data(this.id); - $.each(data.threads, function(email_id, email_data) { + for (let [email_id, email_data] of Object.entries(data.threads)) { api.dom.email_cache[email_id] = email_data; - }); + } } return api.dom.email_cache[this.id]; }, diff --git a/test/test.locale.js b/test/test.locale.js index bb2b6468..7641b43a 100644 --- a/test/test.locale.js +++ b/test/test.locale.js @@ -1,7 +1,7 @@ "use strict"; let assert = require('assert'); let Gmail = require('../src/gmail').Gmail; -let gmail = new Gmail(); +let gmail = new Gmail(false); describe("Locale-parsing", () => { it("Recognizes consistently cased locales", () => { diff --git a/test/test.new.js b/test/test.new.js index d4e8efb9..4fc44150 100644 --- a/test/test.new.js +++ b/test/test.new.js @@ -1,7 +1,7 @@ "use strict"; let assert = require('assert'); let Gmail = require('../src/gmail').Gmail; -let gmail = new Gmail(); +let gmail = new Gmail(false); // prep cache for tests const validEmailLegacyId = "16a0d1f820d515e2"; diff --git a/test/test.parsing.js b/test/test.parsing.js index 9e5e4ab3..815fcb69 100644 --- a/test/test.parsing.js +++ b/test/test.parsing.js @@ -1005,7 +1005,7 @@ let Gmail = require("../src/gmail").Gmail; describe("Test tools for parsing fd-embedded-data", () => { var xhrDataJSON = require("./testdata-parser-json/testdata-parser-fd-embedded.json"); - var gmail = new Gmail(); + var gmail = new Gmail(false); var parsed = gmail.tools.parse_fd_embedded_json(xhrDataJSON); it("JSON Data is an array of 5 elements", () => { diff --git a/test/test.tools.js b/test/test.tools.js index 701efd08..c44e87d4 100644 --- a/test/test.tools.js +++ b/test/test.tools.js @@ -1,7 +1,7 @@ "use strict"; let assert = require('assert'); let Gmail = require('../src/gmail').Gmail; -let gmail = new Gmail(); +let gmail = new Gmail(false); const testData = require("./testdata-parser.js");