Skip to content

Commit

Permalink
Instanciate one bridge/proxy/backend by iframe
Browse files Browse the repository at this point in the history
  • Loading branch information
davinov committed Aug 28, 2017
1 parent c170640 commit 5ec21a8
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 168 deletions.
3 changes: 2 additions & 1 deletion shells/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
{
"matches": ["<all_urls>"],
"js": ["build/hook.js"],
"run_at": "document_start"
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["<all_urls>"],
Expand Down
4 changes: 3 additions & 1 deletion shells/chrome/src/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function handshake (e) {
if (e.data.source === 'vue-devtools-proxy' && e.data.payload === 'init') {
window.removeEventListener('message', handshake)

console.log('handshaked!')
let listeners = []
const bridge = new Bridge({
listen (fn) {
Expand All @@ -26,7 +27,7 @@ function handshake (e) {
payload: data
}, '*')
}
})
}, document.URL)

bridge.on('shutdown', () => {
listeners.forEach(l => {
Expand All @@ -35,6 +36,7 @@ function handshake (e) {
listeners = []
})

console.log('init backend', bridge.frameURL)
initBackend(bridge)
}
}
38 changes: 24 additions & 14 deletions shells/chrome/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,52 @@
const ports = {}

chrome.runtime.onConnect.addListener(port => {
let tab
let tabId
let frameId, frameURL
let name
if (isNumeric(port.name)) {
tab = port.name
let id
if (isNumeric(port.name.split('#')[0])) {
tabId = +port.name.split('#')[0]
frameURL = port.name.split('#')[1]
id = port.name
name = 'devtools'
installProxy(+port.name)
installProxy(tabId, id)
} else {
tab = port.sender.tab.id
tabId = port.sender.tab.id
frameId = port.sender.frameId
frameURL = port.sender.url
id = tabId + '#' + frameURL
console.log('heard of backend for tab#frame ', id)
name = 'backend'
}

if (!ports[tab]) {
ports[tab] = {
if (!ports[id]) {
ports[id] = {
devtools: null,
backend: null
}
}
ports[tab][name] = port
ports[id][name] = port

if (ports[tab].devtools && ports[tab].backend) {
doublePipe(tab, ports[tab].devtools, ports[tab].backend)
if (ports[id].devtools && ports[id].backend) {
doublePipe(id, ports[id].devtools, ports[id].backend)
}
})

function isNumeric (str) {
return +str + '' === str
}

function installProxy (tabId) {
function installProxy(tabId, portId) {
chrome.tabs.executeScript(tabId, {
file: '/build/proxy.js'
file: '/build/proxy.js',
allFrames: true
}, function (res) {
if (!res) {
ports[tabId].devtools.postMessage('proxy-fail')
ports[portId].devtools.postMessage('proxy-fail')
console.log('proxy fails', portId)
} else {
console.log('injected proxy to tab ' + tabId)
console.log('injected proxy to all frames of tab ' + tabId)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion shells/chrome/src/devtools-background.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function createPanelIfHasVue () {
chrome.devtools.inspectedWindow.eval(
'!!(window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue)',
function (hasVue) {
if (!hasVue || created) {
if (created) { //!hasVue ||
return
}
clearInterval(checkVueInterval)
Expand Down
84 changes: 54 additions & 30 deletions shells/chrome/src/devtools.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,17 @@
// this script is called when the VueDevtools panel is activated.

import { initDevTools } from 'src/devtools'
import { initDevTools, registerFrame } from 'src/devtools'
import Bridge from 'src/bridge'

initDevTools({

/**
* Inject backend, connect to background, and send back the bridge.
*
* @param {Function} cb
*/

connect (cb) {
// 1. inject backend code into page
injectScript(chrome.runtime.getURL('build/backend.js'), () => {
// 2. connect to background to setup proxy
const port = chrome.runtime.connect({
name: '' + chrome.devtools.inspectedWindow.tabId
})
let disconnected = false
port.onDisconnect.addListener(() => {
disconnected = true
})

const bridge = new Bridge({
listen (fn) {
port.onMessage.addListener(fn)
},
send (data) {
if (!disconnected) {
port.postMessage(data)
}
}
})
// 3. send a proxy API to the panel
cb(bridge)
})
connect(cb) {
cb()
},

/**
Expand All @@ -44,20 +20,37 @@ initDevTools({
* @param {Function} reloadFn
*/

onReload (reloadFn) {
onReload(reloadFn) {
chrome.devtools.network.onNavigated.addListener(reloadFn)
}
})


function handleRes (res) {
if (res.type === 'document') {
createPortForSubFrame(res.url)
}
}

// Search for iframes...
// ...on devtool panel load
chrome.devtools.inspectedWindow.getResources(function (res) {
res.map(handleRes)
})
// ...when they are added to the page afterwards
chrome.devtools.inspectedWindow.onResourceAdded.addListener(handleRes)


/**
* Inject a globally evaluated script, in the same context with the actual
* user app.
*
* @param {String} scriptName
* @param {String} [frameURL]
* @param {Function} cb
*/

function injectScript (scriptName, cb) {
function injectScriptInFrame (scriptName, frameURL, cb) {
const src = `
(function() {
var script = document.constructor.prototype.createElement.call(document, 'script');
Expand All @@ -66,10 +59,41 @@ function injectScript (scriptName, cb) {
script.parentNode.removeChild(script);
})()
`
chrome.devtools.inspectedWindow.eval(src, function (res, err) {
chrome.devtools.inspectedWindow.eval(src, { frameURL: frameURL }, function (res, err) {
if (err) {
console.log(err)
}
cb()
})
}

function createPortForSubFrame(frameURL) {
console.log('Frame added:', frameURL)
// 1. inject backend code into frame
injectScriptInFrame(chrome.runtime.getURL('build/backend.js'), frameURL, () => {
// 2. connect to background to setup proxy
const port = chrome.runtime.connect({
name: '' + chrome.devtools.inspectedWindow.tabId + '#' + frameURL,
})
let disconnected = false
port.onDisconnect.addListener(() => {
disconnected = true
})

const bridge = new Bridge({
listen(fn) {
port.onMessage.addListener(fn)
},
send(data) {
if (!disconnected) {
port.postMessage(data)
}
}
}, port.name)
// 3. send the new frame along with the proxy API to the panel
registerFrame({
url: frameURL,
bridge: bridge
})
})
}
47 changes: 27 additions & 20 deletions shells/chrome/src/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,37 @@
// to the chrome runtime API. It serves as a proxy between the injected
// backend and the Vue devtools panel.

const port = chrome.runtime.connect({
name: 'content-script'
})
// Install the proxy only once
if (!window.__VUE_DEVTOOL_PROXY_INSTALLED__) {
window.__VUE_DEVTOOL_PROXY_INSTALLED__ = true

port.onMessage.addListener(sendMessageToBackend)
window.addEventListener('message', sendMessageToDevtools)
port.onDisconnect.addListener(handleDisconnect)
const port = chrome.runtime.connect({
name: 'content-script'
})

sendMessageToBackend('init')
port.onMessage.addListener(sendMessageToBackend)
window.addEventListener('message', sendMessageToDevtools)
port.onDisconnect.addListener(handleDisconnect)

function sendMessageToBackend (payload) {
window.postMessage({
source: 'vue-devtools-proxy',
payload: payload
}, '*')
}
console.log('proxy added for frame ', window.location.href)
sendMessageToBackend('init')

function sendMessageToDevtools (e) {
if (e.data && e.data.source === 'vue-devtools-backend') {
port.postMessage(e.data.payload)
function sendMessageToBackend(payload) {
window.postMessage({
source: 'vue-devtools-proxy',
payload: payload
}, '*')
}

function sendMessageToDevtools(e) {
if (e.data && e.data.source === 'vue-devtools-backend') {
port.postMessage(e.data.payload)
}
}

function handleDisconnect() {
window.removeEventListener('message', sendMessageToDevtools)
sendMessageToBackend('shutdown')
}
}

function handleDisconnect () {
window.removeEventListener('message', sendMessageToDevtools)
sendMessageToBackend('shutdown')
}
12 changes: 3 additions & 9 deletions src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { highlight, unHighlight, getInstanceRect } from './highlighter'
import { initVuexBackend } from './vuex'
import { initEventsBackend } from './events'
import { stringify, classify, camelize } from '../util'
import { getDocumentTarget, setDocumentTarget, getAllTargets } from './target-document'
import path from 'path'

// Use a custom basename functions instead of the shimed version
Expand Down Expand Up @@ -42,6 +41,7 @@ export function initBackend (_bridge) {
}

function connect () {
console.log('connect')
hook.currentTab = 'components'
bridge.on('switch-tab', tab => {
hook.currentTab = tab
Expand Down Expand Up @@ -77,13 +77,6 @@ function connect () {
flush()
})

bridge.on('change-target', (id) => {
const target = getAllTargets().find(t => t.id === id)
if (target) {
setDocumentTarget(target.doc)
scan()
}
})
bridge.on('refresh', scan)
bridge.on('enter-instance', id => highlight(instanceMap.get(id)))
bridge.on('leave-instance', unHighlight)
Expand Down Expand Up @@ -111,10 +104,11 @@ function connect () {
*/

function scan () {
console.log('scan')
rootInstances.length = 0
let inFragment = false
let currentFragment = null
walk(getDocumentTarget(), function (node) {
walk(document, function (node) {
if (inFragment) {
if (node === currentFragment._fragmentEnd) {
inFragment = false
Expand Down
1 change: 1 addition & 0 deletions src/backend/target-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function packTarget (doc) {

export function getAllTargets () {
const targets = [packTarget(document)]
console.log('find targets', findTargetsInElement(targets[0]))
return targets.concat(findTargetsInElement(targets[0]))
}

Expand Down
3 changes: 2 additions & 1 deletion src/bridge.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { EventEmitter } from 'events'

export default class Bridge extends EventEmitter {
constructor (wall) {
constructor(wall, portName) {
super()
// Setting `this` to `self` here to fix an error in the Safari build:
// ReferenceError: Cannot access uninitialized variable.
// The error might be related to the webkit bug here:
// https://bugs.webkit.org/show_bug.cgi?id=171543
const self = this
self.portName = portName
self.setMaxListeners(Infinity)
self.wall = wall
wall.listen(message => {
Expand Down
Loading

0 comments on commit 5ec21a8

Please sign in to comment.