Tuesday, April 14, 2020

Chrome Extension - Mouse Gestures

Google Chrome extensions aim to add extra functionality to the browser and/or separate pages. The example below demonstrates how to create a simple extension from scratch. The functionality will be implemented - navigation back and forward with mouse gestures.

The core component of every Chrome extension is the manifest.json file. So let's create the base version of the file:

{
  "name": "Mouse Gestures",
  "version": "1.0",
  "manifest_version": 2,
  "browser_action": {
    "default_popup": "index.html"
  },
  "content_security_policy": "script-src 'self' 'sha256-GgRxrVOKNdB4LrRsVPDSbzvfdV4UqglmviH9GoBJ5jk='; object-src 'self'"
}

As declared in the manifest.json file, we have to add the index.html file. It could be any simple 'hello-world' html file like this:

<!DOCTYPE html>
<html lang="en">
<body>
    Hello, I am an awesome chrome extension!!
</body>
</html>

So, the first version of the extension is ready, let's install it. Open chrome://extensions/ page, turn on the 'Developer mode' toggle on the top panel, then press the 'Load unpacked' button and select the folder with the extension we just created. The new extension appeared on the list and the 'M' icon appeared on the top panel too. If we click it we can see the popup:



Adding icons.

Add the icons section to the manifest.json file:

"icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }

To make it working property, 3 icons (16x16, 48x48, 128x128) must be provided and put into the 'icons' folder. After that press 'refresh' button on the extension:

The extension will be reloaded and the new icon appeared:

And on the top panel as well:



Adding background file.

To make the extension able to run initial script and send messages to separate tabs, we have to add the background.js file with the following content:

// Called when the user clicks on the browser action
chrome.browserAction.onClicked.addListener(function (tab) {
    //Send a message to the active tab
    chrome.tabs.query({ active: true, currentWindow: true },
        function (tabs) {
            var activeTab = tabs[0];
            chrome.tabs.sendMessage(activeTab.id,
                { "message": "clicked_browser_action" }
            );
        });
});

To receive the messages we need the content.js file:

chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
        if (request.message === "clicked_browser_action") {
            console.log('message received');
        }
    }
);

The following changes must be done in the manifest.json file:

{
  "name": "Mouse Gestures",
  "version": "1.0",
  "manifest_version": 2,
  "browser_action": {},
  "background": {
    "scripts": [
      "background.js"
    ]
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "content.js"
      ]
    }
  ],
  "content_security_policy": "script-src 'self' 'sha256-GgRxrVOKNdB4LrRsVPDSbzvfdV4UqglmviH9GoBJ5jk='; object-src 'self'",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

Notice that we deleted the "default_popup" from the "browser_action" section, but the section itself must be remained.

So now if we reload the extension and click the icon, we can see the message in the console: 'message received'

Adding options page.

Add the following section to the manifest.json file:

"options_ui": {
    "page": "options.html",
    "open_in_tab": false
  },
  "permissions": [
    "storage"
]

Add options.html and options.js files.

options.html:

<!DOCTYPE html>
<html>

<head>
    <title>Options Page</title>
</head>

<body>

    Line Color:
    <select id="color">
        <option value="red">red</option>
        <option value="green">green</option>
        <option value="blue">blue</option>
        <option value="yellow">yellow</option>
        <option value="black">black</option>
    </select>

    <div id="status"></div>
    <button id="save">Save</button>

    <script src="options.js"></script>
</body>

</html>

options.js:

document.addEventListener('DOMContentLoaded', restoreOptions);
document.getElementById('save').addEventListener('click', saveOptions);

// Saves options to chrome.storage
function saveOptions() {
    var color = document.getElementById('color').value;
    chrome.storage.sync.set({
        trackColor: color
    }, function () {
        // Update status to let user know options were saved.
        var status = document.getElementById('status');
        status.textContent = 'Options saved.';
        setTimeout(function () {
            status.textContent = '';
        }, 750);
    });
}

// Restores options stored in chrome.storage.
function restoreOptions() {
    chrome.storage.sync.get({ trackColor: 'green' }, function (items) {
        document.getElementById('color').value = items.trackColor;
    });
}

Here we using chrome.storage to keep the data (trackColor) persistent.

Now, after reloading the extension if right click on the icon, the 'options' menu is available and we can see the options page:



Adding mouse gesture logic.

Finally, to process the mouse gesture logic, I updated the content.js file, created drawing the line, activating/deactivating the extension:


let gestureTrack = [];
let trackColor = 'green';
let active = true;

chrome.storage.sync.get({ trackColor: 'green', active: true }, function (items) {
    trackColor = items.trackColor;
    active = items.active;
});


chrome.storage.onChanged.addListener(function (changes, namespace) {
    chrome.storage.sync.get('trackColor', function (items) {
        trackColor = items.trackColor;
    });
    chrome.storage.sync.get('active', function (items) {
        active = items.active;
    });
});

window.addEventListener('contextmenu', function (e) {
    if (gestureTrack.length > 0) {
        e.preventDefault();
    }
}, false);

document.addEventListener('mousemove', e => {
    if (e.buttons == 0) {
        // release track
        if (gestureTrack.length > 5) {
            gestureTrack[0].x < gestureTrack[gestureTrack.length - 1].x ? history.forward() : history.back();
        }

        gestureTrack.forEach(p => {
            p.element.remove();
        });
        gestureTrack = [];
    }
    else if (e.buttons == 2 && active) {
        // right button is down
        const trackPoint = document.createElement('div');
        trackPoint.classList.add('trackPoint');
        trackPoint.style.top = `${e.clientY}px`;
        trackPoint.style.left = `${e.clientX}px`;
        trackPoint.style.border = `3px solid ${trackColor}`;
        document.body.appendChild(trackPoint);

        gestureTrack.push({ x: e.clientX, y: e.clientY, element: trackPoint });
    }
});

So now when holding right mouse button pressed and moving mouse left, we call the history.back(), if moving right we call history.forward(). Also, we draw pseudo line to track the gesture:

That's it. All the code could be found here https://github.com/AndrewBuntsev/mouse-gestures

No comments:

Post a Comment