How to make Chrome Extension run for each new Ifra

2020-07-22 17:39发布

问题:

I created a Chrome Extension as a solution to override the helpText bubbles in SalesForce Console pages. The helpText bubbles show up the text without the ability to link URLs. It looks like this:

The extension is taking the helpText bubble (which in the SalesForce console window, is inside an iFrame) and makes the URL click-able. It also adds word wrap and marks the links in blue.

The solution works fine when the page loads with the initial iFrame (or iFrames) on it, meaning when you open the SalesForce console the first time (https://eu3.salesforce.com/console).
When a new tab is created at the SalesForce console, my inject script doesn't run.

Can you please assist in understanding how to inject the script on each and every new Tab SalesForce Console is creating?

The Extension as follows:

manifest.js:

    {
   "browser_action": {
      "default_icon": "icons/icon16.png"
   },
   "content_scripts": [ {
      "all_frames": true,
      "js": [ "js/jquery/jquery.js", "src/inject/inject.js" ],
      "matches": [ "https://*.salesforce.com/*", "http://*.salesforce.com/*" ]
   } ],
   "default_locale": "en",
   "description": "This extension Fix SalesForce help bubbles",
   "icons": {
      "128": "icons/icon128.png",
      "16": "icons/icon16.png",
      "48": "icons/icon48.png"
   },
   "manifest_version": 2,
   "name": "--Fix SalesForce bubble text--",
   "permissions": [ "https://*.salesforce.com/*", "http://*.salesforce.com/*" ],
   "update_url": "https://clients2.google.com/service/update2/crx",
   "version": "5"
}

And this is the inject.js:

chrome.extension.sendMessage({}, function(response) {
  var readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete") {
      clearInterval(readyStateCheckInterval);

      var frame = jQuery('#servicedesk iframe.x-border-panel');
      frame = frame.contents();

      function linkify(inputText) {
        var replacedText, replacePattern1, replacePattern2, replacePattern3;
        var originalText = inputText;

        //URLs starting with http://, https://, file:// or ftp://
        replacePattern1 = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
        replacedText = inputText.replace(replacePattern1, '<a href="$1" style="color: blue;" target="_blank">$1</a>');

        //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
        replacePattern2 = /(^|[^\/f])(www\.[\S]+(\b|$))/gim;

        replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" style="color: blue;" target="_blank">$2</a>');

        //Change email addresses to mailto:: links.
        replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
        replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

        //If there are hrefs in the original text, let's split
        // the text up and only work on the parts that don't have urls yet.
        var count = originalText.match(/<a href/g) || [];

        if(count.length > 0){
          var combinedReplacedText;
          //Keep delimiter when splitting
          var splitInput = originalText.split(/(<\/a>)/g);

          for (i = 0 ; i < splitInput.length ; i++){
            if(splitInput[i].match(/<a href/g) == null){
              splitInput[i] = splitInput[i].replace(replacePattern1, '<a href="$1" target="_blank">$1</a>').replace(replacePattern2, '$1<a href="http://$2" style="color: blue;" target="_blank">$2</a>').replace(replacePattern3, '<a href="mailto:$1">$1</a>');
            }
          }
          combinedReplacedText = splitInput.join('');
          return combinedReplacedText;
        } else {
          return replacedText;
        }
      }

      var helpOrbReady = setInterval(function() {
        var helpOrb = frame.find('.helpOrb');
        if (helpOrb) {
          clearInterval(helpOrbReady)
        } else {
          return;
        }

        helpOrb.on('mouseout', function(event) {
          event.stopPropagation();
          event.preventDefault();
          setTimeout(function() {
            var helpText = frame.find('.helpText')
            helpText.css('display', 'block');
            helpText.css('opacity', '1');
            helpText.css('word-wrap', 'break-word');

            var text = helpText.html()
            text = text.substr(text.indexOf('http'))
            text = text.substr(0, text.indexOf(' '))

            var newHtml = helpText.html()
            helpText.html(linkify(newHtml))
          }, 500); });
      }, 1000);
    }
  }, 1000);
});

回答1:

It is possible (I have not tested it, but it sounds plausible from a few questions I've seen here) that Chrome does not automatically inject manifest-specified code into newly-created <iframe> elements.

In that case, you will have to use a background script to re-inject your script:

chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
  if(request.reinject) {
    chrome.tabs.executeScript(
      sender.tab.id,
      { file: "js/jquery/jquery.js", "all_frames": true },
      function(){
        chrome.tabs.executeScript(
          sender.tab.id,
          { file: "js/inject/inject.js", "all_frames": true }
        );
      }
    );
});

Content script:

// Before everything: include guard, ensure injected only once
if(injected) return;
var injected = true;

function onNewIframe(){
  chrome.runtime.sendMessage({reinject: true});
}

Now, I have many questions about your code, which are not directly related to your question.

  • Why the pointless sendMessage wrapper? No-one is even listening, so your code basically returns with an error set.
  • Why all the intervals? Use events instead of polling.
    • If you are waiting on document to become ready, jQuery offers $(document).ready(...)
    • If you're waiting on DOM modifications, learn to use DOM Mutation Observers, as documented and as outlined here or here. This would be, by the way, the preferred way to call onNewIframe().