Chrome: Communication Between Tabs
Tags: javascript, chrome, sandbox, webdev
Published: 2019-08-26 12:13:59

---
title: Chrome: Communication Between Tabs
published: true
description: When putting a recent talk together about Asynchronous JavaScript, I was looking to build a browser controlled presentation. I know that browser tabs are sandboxed, but have seen this type of functionality (one tab controlling another) ...
tags: JavaScript, Chrome, Sandbox, WebDev
series: Chrome Development
cover_image: https://thepracticaldev.s3.amazonaws.com/i/wjsmwrrbjq9sxswaenqb.png
---
When putting a recent talk together about Asynchronous JavaScript, I was looking to build a ***browser controlled*** presentation that allowed the controlling browser tab to control the presentation tab. In particular, I was looking at managing three things:

1. Slide Position
2. Slide Font Size
3. Slide Actions

For the third one, Slide Actions, I was looking to trigger display of some code (preferably in Developer Tools > Console) as well as potentially running the code.

As a long-time front-end developer, I know that browser tabs are sandboxed, but have seen this ***type of functionality*** over time ... but remembering where was daunting. I also wanted to do the research and not look into (felt like cheating) some of the presentation tools (such as [reveal.js](https://revealjs.com/)) that have this functionality.

What I came across was **BroadcastChannel** and it is supported in Firefox and Chrome per [caniuse.com](https://caniuse.com/#feat=broadcastchannel). Since I can't imaging trying to give a presentation using IE or Edge, I considered this great information.

## Setup Channels

Use of this functionality wound up being pretty simple ... this code initiated the process in the ***index.html*** JavaScript code (***_functionality.js***) ...

```javascript
const pnChannel = new BroadcastChannel('le-slides-position');
const fsChannel = new BroadcastChannel('le-slides-font-size');
const anChannel = new BroadcastChannel('le-slides-actions');
```
In the ***_navigation.js***, ***_font-sizing.js***, and ***_code-examples*** files, there are matching declarations ...

```javascript
// _navigation.js
const channel = new BroadcastChannel('le-slides-position');

// _font-sizing.js
const channel = new BroadcastChannel('le-slides-font-size');

// _code-examples.js
const channel = new BroadcastChannel('le-slides-actions');
```

**NOTE:** Each of these lines is in a separate file, hence the use of ***const channel*** on each line.

## Channel Communication

Here, we'll just examine sending data from the controlling **index.html**, ***_functionality,js*** code ...

```javascript
const actions = {
  init: (force = false) => {
    if (!initFired || force) {
      fsChannel.postMessage('init');
      pnChannel.postMessage('init');
      anChannel.postMessage('init');
      initFired = true;
    }
  },
			
  up: () => {
    if (!upButton.hasClass('disabled')) {
      fsChannel.postMessage('trigger-up');				
    }
  },
  reset: () => {
    fsChannel.postMessage('trigger-reset');			
  },
  down: () => {
    if (!downButton.hasClass('disabled')) {
      fsChannel.postMessage('trigger-down');				
    }
  },
	
  previous: () => {
    if (!previousButton.hasClass('disabled')) {
      pnChannel.postMessage('trigger-previous');				
    }
  },
  next: () => {
    if (!nextButton.hasClass('disabled')) {
      pnChannel.postMessage('trigger-next');
    }
  },
			
  triggerAction: (action) => {
    anChannel.postMessage(action);
  }
};
```

## Position Channel

Now, looking at the **pnChannel** (position channel) ... we can see that the ***.onmessage*** funcitonality expects a state.  The state sent can include data, in this case what the current index is ... also, additional data is sent, such as previous and next disable states and these buttons can be adjusted appropriately.

```javascript
pnChannel.onmessage = (states) => {
  cardIndex = states.data.currentIndex;
  updateContent();
		
  if (states.data.previousDisabled) {
    previousButton.addClass('disabled');
  } else {
    previousButton.removeClass('disabled');
  }

  if (states.data.nextDisabled) {
    nextButton.addClass('disabled');
  } else {
    nextButton.removeClass('disabled');
  }
};
```

In the **_navigation.js** file, there it recieves a ***triggerAction*** whose data is actually used to execute some functionality ...

```javascript
channel.onmessage = (triggerAction) => {
  actions[triggerAction.data]();
};

const actions = {
  init: () => {
    nextButton.hide();
    previousButton.hide();
  },
  
  'trigger-previous': () => {
    slideStateMachine.next('previous');
  },
  'trigger-next': () => {
    slideStateMachine.next('next');
  },
  
  'report-states': (index) => {
    channel.postMessage({
      currentIndex: index,
      previousDisabled: previousButton.hasClass('disabled'),
      nextDisabled: nextButton.hasClass('disabled')
    });
  }
};
```

With this code, it should become clear that sending a message is simply a matter of utilizing the ***.postMessage*** functionality of a channel.

## Font Sizing Channel

Looking at the **fsChannel** we can see the ***.onmessage*** expects a state again, allowing for the button states to be assigned ...

```javascript
fsChannel.onmessage = (states) => {
  if(states.data.upDisabled) {
    upButton.addClass('disabled');
  } else {
    upButton.removeClass('disabled');
  }

  if(states.data.downDisabled) {
    downButton.addClass('disabled');
  } else {
    downButton.removeClass('disabled');
  }		
};
```

This is connected to the ***_font-sizing.js** code, which again triggers various actions ...

```javascript
channel.onmessage = (triggerAction) => {
  actions[triggerAction.data]();
};

const actions = {
  init: () => {
    upButton.hide();
    downButton.hide();
    resetButton.hide();
  },

  'trigger-up': () => {
    fontStateMachine.next('up');
  },
  'trigger-reset': () => {
    fontStateMachine.next('reset');      
  },
  'trigger-down': () => {
   fontStateMachine.next('down');
  },
  
  'report-states': () => {
    channel.postMessage({
      upDisabled: upButton.hasClass('disabled'),
      downDisabled: downButton.hasClass('disabled')
    });
  }
};
```

## Action Channel

Looking at the **anChannel** we can see that here, the response state data is simply sent to console.log ...

```javascript
anChannel.onmessage = (states) => {
  console.log('action reply:', states.data);
};
```

The associated code in the **_code-examples.js** file is a bit more complicated ...

```javascript
channel.onmessage = (states) => {
  const cardAction = cardActions[states.data];
  if (states.data === 'init') {
    cardAction();
  } else {
    if (cardAction.showDisplay) {
      console.log(cardAction.display);		
    }
    cardAction.fn();		
  }
};
```

In this case, I will admit that I "cheated" a bit for a specific purpose ... I used some JSON data ...

```json
"fn": "triggerImage('queues.png', false)"
```

... and within the **_code-examples.js** ***init*** functionality, I rebuild them as executable functions. Thus, I was able to use a JSON file to control the elements on each screen, as well as what could be "executed" on the presentation screen ...

```javascript
const name = card.options[j].name;
const optionFn = new Function(card.options[j].fn);
cardActions[name] = {
  fn: optionFn,
  showDisplay: card.options[j].showFn,
  display: card.options[j].fn
};
```

## Conclusions

I learned a lot of exciting things with the project and the code is available on my GitHub account.  I'd rather not give it away directly, so I am not going to link to it here.

The content in my article [JavaScript Enjoys Your Tears](https://dev.to/rfornal/javascript-enjoys-your-tears-4el) is what I use to present [Single-Threaded and Asynchronous JavaScript?](https://sessionize.com/s/bob-fornal/single-threaded_and_asynchronous_ja/22652).

This was an interesting project and at some point, I can see myself working this into a presentation, in and of itself.