9

I'm porting a Chrome extension to Firefox WebExtensions and I'm stuck on finding a workaround for chrome.declarativeContent.onPageChanged.

My FF Webextension contains a Page Action that must be shown when navigating on certain websites. However, none of the listeners in the available API seem to allow this.

In particular I've tried:

chrome.runtime.onInstalled.addListener(onChange);
chrome.tabs.onCreated.addListener(onChange);
chrome.tabs.onActivated.addListener(onChange);
chrome.tabs.onUpdated.addListener(onChange);
chrome.webNavigation.onDOMContentLoaded(onChange);
chrome.webNavigation.onCreatedNavigationTarget(onChange);

Are there any known workarounds?

ktouchie
  • 371
  • 1
  • 3
  • 9

3 Answers3

11

You'll have to show the pageAction manually because declarativeContent API is not yet supported.

chrome.pageAction.show(tabId);
chrome.pageAction.hide(tabId);

In case the rules are based on URL matching the implementation is quite easy:

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    if (changeInfo.status === 'complete' && tab.url.match(/something/)) {
        chrome.pageAction.show(tabId);
    } else {
        chrome.pageAction.hide(tabId);
    }
});

However in case it's DOM element-based you'll have to use a content script:

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    if (changeInfo.status === 'complete' && tab.url.match(/something/)) {
        chrome.tabs.executeScript(tabId, {
            code: 'document.querySelector(' + JSON.stringify(someSelector) + ')'
        }, function(results) {
            if (results[0]) {
                chrome.pageAction.show(tabId);
            } else {
                chrome.pageAction.hide(tabId);
            }
        });
    } else {
        chrome.pageAction.hide(tabId);
    }
});

This is a very simplified code. It can be enhanced to look for elements while a page is loading.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • In case of a content-script based approach `onUpdated` listener may not be even needed. – Xan Aug 31 '16 at 15:10
  • `chrome.tabs.onUpdated.addListener` works once I'm navigating to different sites, but not when I first open a tab. Will post my workaround below! – ktouchie Aug 31 '16 at 15:31
  • FYI: Your code will inject and execute the content script multiple times on most pages. The `tabs.onUpdated` event is often fired multiple times per page load with `tab.status === 'complete'` (a bug IMO). Possible: look for `changeInfo.url && changeInfo.status==='complete'`. For a more detailed look at the events available see [tabs.onUpdated.addListener's changeInfo.status goes from undefined to Complete without ever going to loading](http://stackoverflow.com/a/39033812/3773011). Code in that answer can help investigate the events available and determine what criteria works for the situation. – Makyen Aug 31 '16 at 16:53
  • Correct. But sometimes there's nothing wrong with multiple execution in case the page action should be hidden once the element disappears from the page. Although in that case a MutationObserver or setInterval callback seems a better choice. Actually, I've meant to use `changeInfo.status` initially, so thanks, the answer is updated. – wOxxOm Aug 31 '16 at 16:54
  • For the simple case (execute & terminate) of the code here, multiple execution is OK. People run into problems when they multiply inject something that does not fully terminate. Yeah, for CSS selectors in `chrome.declarativeContent` there is not, really, an *effective* solution other than injecting a stay-resident content script into every page matching the URL (easier? with *manifest.json* `"content_scripts"`) that uses a MutationObserver or `setTimeout` then sends a message to the background script upon match. `chrome.declarativeContent` is, after all, specifically a shortcut for doing that. – Makyen Aug 31 '16 at 17:16
  • I would recommend not checking for `changeInfo.url`. I just saw that at least in Chrome, this is not set during the `'complete'` event. Instead, it would probably make sense to check for `tab.url`. – AndyO Aug 11 '18 at 12:34
  • based on this answer I created a port for an addon, thank you! You can see the changes here: https://github.com/VLCTechHub/publisher-browser-extension/commit/8f86cf95623ff5504d3dc9e478dd8eafa0cceb5e – elmendalerenda Nov 21 '18 at 18:36
0

For simple URL matching, and for new tabs as well as changes to an existing tab, this works:

  • include the tabs permission in the manifest.

.

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
  if (changeInfo.status === 'complete' && tab.url.match(/something/)) {
    chrome.pageAction.show(tabId);
  } else if (changeInfo.status === 'complete') {
    chrome.pageAction.hide(tabId);
  }
});
chrome.tabs.onCreated.addListener(function(tab) {
  if (tab.url && tab.url.match(/something/)) {
    chrome.pageAction.show(tab.id);
  } else if (tab.url) {
    chrome.pageAction.hide(tab.id);
  }
});
Rob Hawkins
  • 451
  • 1
  • 5
  • 17
-1

The best equivalent so far seems to be:

var target = "<all_urls>";
chrome.webRequest.onCompleted.addListener(onChange, {urls: [target]});

(onChange() includes matching the current URL with a regex and then chrome.pageAction.show(tabInfo.id);)

ktouchie
  • 371
  • 1
  • 3
  • 9
  • 3
    It'll fire on all types of web requests, including images and scripts and styles. Use filtering to limit it to the main frame only: `chrome.webRequest.onCompleted.addListener(onChange, {urls: [target], types: ['main_frame']});` – wOxxOm Aug 31 '16 at 15:40