File indexing completed on 2024-04-28 05:32:50

0001 /*
0002     Copyright (C) 2017 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     This program is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU General Public License as
0006     published by the Free Software Foundation; either version 3 of
0007     the License, or (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 function sendEnvironment() {
0019     var browser = "";
0020 
0021     var ua = navigator.userAgent;
0022     // Try to match the most derived first
0023     if (ua.match(/vivaldi/i)) {
0024         browser = "vivaldi";
0025     } else if(ua.match(/OPR/i)) {
0026         browser = "opera";
0027     } else if(ua.match(/chrome/i)) {
0028         browser = "chromium";
0029         // Apparently there is no better way to distinuish chromium from chrome
0030         for (i in window.navigator.plugins) {
0031             if (window.navigator.plugins[i].name === "Chrome PDF Viewer") {
0032                 browser = "chrome";
0033                 break;
0034             }
0035         }
0036     } else if(ua.match(/firefox/i)) {
0037         browser = "firefox";
0038     }
0039 
0040     sendPortMessage("settings", "setEnvironment", {browserName: browser});
0041 }
0042 
0043 function sendSettings() {
0044     SettingsUtils.get().then((items) => {
0045         sendPortMessage("settings", "changed", items);
0046     });
0047 }
0048 
0049 // activates giveb tab and raises its window, used by tabs runner and mpris Raise command
0050 function raiseTab(tabId) {
0051 // first activate the tab, this means it's current in its window
0052     chrome.tabs.update(tabId, {active: true}, function (tab) {
0053 
0054         if (chrome.runtime.lastError || !tab) { // this "lastError" stuff feels so archaic
0055             // failed to update
0056             return;
0057         }
0058 
0059         // then raise the tab's window too
0060         chrome.windows.update(tab.windowId, {focused: true});
0061     });
0062 }
0063 
0064 // Debug
0065 // ------------------------------------------------------------------------
0066 //
0067 
0068 function printDebug(payload, fn) {
0069     let hostLabel = "Host";
0070     if (payload.category && payload.category !== "default") {
0071         hostLabel += " [" + payload.category + "]";
0072     }
0073 
0074     const hostLabelColor = "#3daee9"; // Breeze highlight color
0075 
0076     if (payload.line && payload.file) {
0077         const fileName = payload.file.split("/").pop();
0078         fn("%c%s: %c%s %c[%s:%i]",
0079            "color: " + hostLabelColor,
0080            hostLabel,
0081            "", // reset CSS
0082            payload.message,
0083            "color: #999",
0084            fileName,
0085            payload.line);
0086     } else {
0087         fn("%c%s: %c%s",
0088            "color: " + hostLabelColor,
0089            hostLabel,
0090            "", // reset CSS
0091            payload.message);
0092     }
0093 }
0094 
0095 addCallback("debug", "debug", function(payload) {
0096     if (payload.severity === "info") {
0097         printDebug(payload, console.info);
0098     } else {
0099         printDebug(payload, console.log);
0100     }
0101 }
0102 )
0103 
0104 addCallback("debug", "warning", function(payload) {
0105     if (payload.severity === "critical" || payload.severity === "fatal") {
0106         printDebug(payload, console.error);
0107     } else {
0108         printDebug(payload, console.warn);
0109     }
0110 }
0111 )
0112 
0113 // System
0114 // ------------------------------------------------------------------------
0115 //
0116 
0117 // When connecting to native host fails (e.g. not installed), we immediately get a disconnect
0118 // event immediately afterwards. Also avoid infinite restart loop then.
0119 var receivedMessageOnce = false;
0120 
0121 var portStatus = "";
0122 var portLastErrorMessage = undefined;
0123 
0124 function updateBrowserAction() {
0125     if (portStatus === "UNSUPPORTED_OS" || portStatus === "STARTUP_FAILED") {
0126         chrome.browserAction.setIcon({
0127             path: {
0128                 "16": "icons/plasma-disabled-16.png",
0129                 "32": "icons/plasma-disabled-32.png",
0130                 "48": "icons/plasma-disabled-48.png",
0131                 "128": "icons/plasma-disabled-128.png"
0132             }
0133         });
0134     }
0135 
0136     if (portLastErrorMessage && receivedMessageOnce) {
0137         chrome.browserAction.setBadgeText({ text: "!" });
0138         chrome.browserAction.setBadgeBackgroundColor({ color: "#da4453" }); // breeze "negative" color
0139     } else {
0140         chrome.browserAction.setBadgeText({ text: "" });
0141     }
0142 }
0143 updateBrowserAction();
0144 
0145 // Check for supported platform to avoid loading it on e.g. Windows and then failing
0146 // when the extension got synced to another device and then failing
0147 chrome.runtime.getPlatformInfo(function (info) {
0148     if (!SUPPORTED_PLATFORMS.includes(info.os)) {
0149         console.log("This extension is not supported on", info.os);
0150         portStatus = "UNSUPPORTED_OS";
0151         updateBrowserAction();
0152         return;
0153     }
0154 
0155     connectHost();
0156 });
0157 
0158 function connectHost() {
0159     port = chrome.runtime.connectNative("org.kde.plasma.browser_integration");
0160 
0161     port.onMessage.addListener(function (message) {
0162         var subsystem = message.subsystem;
0163         var action = message.action;
0164 
0165         let isReply = message.hasOwnProperty("replyToSerial");
0166         let replyToSerial = message.replyToSerial;
0167 
0168         if (!isReply && (!subsystem || !action)) {
0169             return;
0170         }
0171 
0172         if (portStatus) {
0173             portStatus = "";
0174             updateBrowserAction();
0175         }
0176 
0177         receivedMessageOnce = true;
0178 
0179         if (isReply) {
0180             let replyResolver = pendingMessageReplyResolvers[replyToSerial];
0181             if (replyResolver) {
0182                 replyResolver(message.payload);
0183                 delete pendingMessageReplyResolvers[replyToSerial];
0184             } else {
0185                 console.warn("There is no reply resolver for message with serial", replyToSerial);
0186             }
0187             return;
0188         }
0189 
0190         if (callbacks[subsystem] && callbacks[subsystem][action]) {
0191             callbacks[subsystem][action](message.payload, action);
0192         } else {
0193             console.warn("Don't know what to do with host message", subsystem, action);
0194         }
0195     });
0196 
0197     port.onDisconnect.addListener(function(port) {
0198         var error = chrome.runtime.lastError;
0199         // Firefox passes in the port which may then have an error set
0200         if (port && port.error) {
0201             error = port.error;
0202         }
0203 
0204         console.warn("Host disconnected", error && error.message);
0205 
0206         // Remove all kde connect menu entries since they won't work without a host
0207         try {
0208             Object.keys(kdeConnectDevices).forEach((deviceId) => {
0209                 callbacks.kdeconnect.deviceRemoved({
0210                     id: deviceId
0211                 });
0212             });
0213         } catch (e) {
0214             console.warn("Failed to cleanup after port disconnect", e);
0215         }
0216 
0217         portLastErrorMessage = error && error.message || "UNKNOWN";
0218         if (receivedMessageOnce) {
0219             portStatus = "DISCONNECTED";
0220 
0221             console.log("Auto-restarting it");
0222             connectHost();
0223         } else {
0224             portStatus = "STARTUP_FAILED";
0225 
0226             console.warn("Not auto-restarting host as we haven't received any message from it before. Check that it's working/installed correctly");
0227         }
0228         updateBrowserAction();
0229     });
0230 
0231     sendEnvironment();
0232     sendSettings();
0233     sendDownloads();
0234 
0235     updatePurposeMenu();
0236 }
0237 
0238 SettingsUtils.onChanged().addListener(() => {
0239     sendSettings();
0240 });
0241 
0242 addRuntimeCallback("settings", "openKRunnerSettings", function () {
0243     sendPortMessage("settings", "openKRunnerSettings");
0244 });
0245 
0246 addRuntimeCallback("settings", "getSubsystemStatus", (message, sender, action) => {
0247     return sendPortMessageWithReply("settings", "getSubsystemStatus");
0248 });
0249 
0250 addRuntimeCallback("settings", "getVersion", () => {
0251     return sendPortMessageWithReply("settings", "getVersion");
0252 });
0253 
0254 addRuntimeCallback("browserAction", "getStatus", (message) => {
0255     let info = {
0256         portStatus,
0257         portLastErrorMessage
0258     };
0259 
0260     return Promise.resolve(info);
0261 });
0262 
0263 addRuntimeCallback("browserAction", "ready", () => {
0264 
0265     // HACK there's no way to tell whether the browser action popup got closed
0266     // None of onunload, onbeforeunload, onvisibilitychanged are fired.
0267     // Instead, we create a port once the browser action is ready and then
0268     // listen for the port being disconnected.
0269 
0270     let browserActionPort = chrome.runtime.connect({
0271         name: "browserActionPort"
0272     });
0273     browserActionPort.onDisconnect.addListener((port) => {
0274         if (port.name !== "browserActionPort") {
0275             return;
0276         }
0277 
0278         // disabling the browser action immediately when opening it
0279         // causes opening to fail on Firefox, so clear the error only when it's being closed.
0280         // Only clear error when it was a transient error, not a startup failure
0281         if (receivedMessageOnce) {
0282             portLastErrorMessage = "";
0283             updateBrowserAction();
0284         }
0285     });
0286 });