3

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:

enter image description here

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.

enter image description here

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);
});
serv-inc
  • 35,772
  • 9
  • 166
  • 188
iDog
  • 137
  • 1
  • 7
  • 1
    It is part of Stack Overflow's etiquette to include code in question (but only the relevant parts!). The reason for that is Stack Overflow's goal: it's **not about helping you**, it's about helping **everyone with the same problem**. A link can go dead, a quote here cannot. That, and the fact that extracting the code is a lot of steps most people don't want to perform. – Xan Jun 02 '14 at 14:49

1 Answers1

2

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().
Xan
  • 74,770
  • 16
  • 179
  • 206
  • Why did you call `executeScript` twice? – serv-inc Sep 20 '17 at 14:12
  • 1
    @serv-inc Because the original question needed two files injected (jQuery and `inject.js`) as content scripts. The function cannot take multiple scripts at once. – Xan Sep 20 '17 at 14:13