Why donate
API Explorer
Upgrade Guide
Creating a New Project
The /quasar.config File
Convert q/app-webpack Project
Browser Compatibility
TypeScript Support
Directory Structure
Commands List
CSS Preprocessors
Page Routing with VueRouter
Lazy Loading - Code Splitting
Handling Assets
Boot Files
Prefetch Feature
API Proxying
Handling Vite
Handling import.meta.env
State Management with Pinia
Lint and Format Code
Testing & Auditing
Developing Mobile Apps
Fetching Data
Opening Dev Server To Public
Quasar CLI with Vite - @quasar/app-vite v3
Types of BEX

One Quasar App can provide a new-tab page, options page, popup, developer-tools page, or UI injected into a web page. Use routes to select the UI for each extension entry point.

New Tab

A new-tab extension replaces the browser’s new-tab page. Configure the appropriate manifest override to point to www/index.html; clicking the extension icon instead uses the manifest’s action or browser_action configuration.

Developer tools, options, and popup

These entry points follow the same pattern: create a route and configure manifest.json to open it. Hash-mode routes work from an extension URL without server-side rewrite rules:

routes.js:

const routes = [
  { path: '/options', component: () => import('@/pages/OptionsPage.vue') },
  { path: '/popup', component: () => import('@/pages/PopupPage.vue') },
  { path: '/devtools', component: () => import('@/pages/DevToolsPage.vue') }
]

Then reference the routes from the manifest:

/src-bex/manifest.json

{
  "manifest_version": 3,

  "action": {
    "default_popup": "www/index.html#/popup"
  },
  "options_page": "www/index.html#/options",
  "devtools_page": "www/index.html#/devtools"
}

Case study: Web Page

This is where the real power comes in. With a little ingenuity we can inject our Quasar application into a web page and use it as an overlay making it seem like our Quasar App is part of the page experience.

Here’s a brief rundown of how you could achieve this:

  • src-bex/my-content-script.js

The idea here is to create an IFrame and add our Quasar app into it, then inject that into the page.

Given our Quasar App might need to take the full height of the window (and thus stop any interaction with the underlying page) we have an event to handle setting the height of the IFrame. By default the IFrame height is just high enough to allow for the Quasar toolbar to show (and in turn allowing interaction with the rest of the page).

/src-bex/my-content-script.js

/**
 * Importing the file below initializes the content script.
 *
 * Warning:
 *   Do not remove the import statement below. It is required for the extension to work.
 *   If you don't need createBridge(), leave it as "import '#q-app/bex/content'".
 */
import { createBridge } from '#q-app/bex/content'

const bridge = createBridge({ debug: false })

/**
 * When the drawer is toggled set the iFrame height to take the whole page.
 * Reset when the drawer is closed.
 */
bridge.on('wb.drawer.toggle', ({ payload }) => {
  if (payload.open) {
    setIFrameHeight('100%')
  } else {
    resetIFrameHeight()
  }
})

const iFrame = document.createElement('iframe')
const defaultFrameHeight = '62px'

/**
 * Set the height of our iFrame housing our BEX
 * @param height
 */
function setIFrameHeight(height) {
  iFrame.height = height
}

/**
 * Reset the iFrame to its default height e.g The height of the top bar.
 */
function resetIFrameHeight() {
  setIFrameHeight(defaultFrameHeight)
}

/**
 * The code below will get everything going. Initialize the iFrame with defaults and add it to the page.
 * @type {string}
 */
iFrame.id = 'bex-app-iframe'
iFrame.width = '100%'
resetIFrameHeight()

// Assign some styling so it looks seamless
Object.assign(iFrame.style, {
  position: 'fixed',
  top: '0',
  right: '0',
  bottom: '0',
  left: '0',
  border: '0',
  zIndex: '9999999', // Make sure it's on top
  overflow: 'visible'
})
;(function () {
  // When the page loads, insert our browser extension app.
  iFrame.src = chrome.runtime.getURL('www/index.html')
  document.body.prepend(iFrame)
})()

We can call this event from our Quasar App any time we know we’re opening the drawer and thus changing the height of the IFrame to allow the whole draw to be visible.

  • src-bex/assets/content.css

Add a margin to the top of our document so our Quasar toolbar doesn’t overlap the actual page content.

.target-some-header-class {
  margin-top: 62px;
}
  • Quasar App (/src)

Then in our Quasar app (/src), we have a function that toggles the drawer and sends an event to the content script telling it to resize the IFrame thus allowing our whole app to be visible:

<q-drawer :model-value="drawerIsOpen" @update:model-value="drawerToggled">
  Some Content
</q-drawer>
import { useQuasar } from 'quasar'
import { ref } from 'vue'

setup () {
  const $q = useQuasar()
  const drawerIsOpen = ref(true)

  async function drawerToggled () {
    const contentPort = $q.bex.portList.find(portName =>
      portName.startsWith('content@my-content-script-')
    )

    if (contentPort === void 0) {
      return
    }

    await $q.bex.send({
      event: 'wb.drawer.toggle',
      to: contentPort,
      payload: {
        open: drawerIsOpen.value
      }
    })

    // Only set this once the promise has resolved so we can see the entire slide animation.
    drawerIsOpen.value = !drawerIsOpen.value
  }

  return { drawerToggled }
}

Now you have a Quasar App running in a web page. You can now trigger other events from the Quasar App that the content script can listen to and interact with the underlying page.

WARNING

Be sure to check your manifest.json file, especially around the reference to my-content-script.js. Note that you can have multiple content scripts. Whenever you create a new one, you need to reference it in the manifest file. Same for any css files created in /src-bex/assets folder.


/src-bex/manifest.json

"content_scripts": [
  {
    "matches": [ "<all_urls>" ],
    "css": [ "assets/content.css" ],
    "js": [ "my-content-script.js" ]
  }
]