Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
bug 622077: add an API call to create a worker from a tab; r=myk
  • Loading branch information
ochameau authored and mykmelez committed Feb 14, 2011
1 parent 15aaa1f commit ab41edc
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 27 deletions.
35 changes: 35 additions & 0 deletions packages/addon-kit/docs/tabs.md
Expand Up @@ -228,4 +228,39 @@ This is an optional argument.
@method
Makes this tab active, which will bring this tab to the foreground.
</api>

<api name="attach">
@method
Create a page mod and attach it to the document in the tab.
**Example**

var tabs = require("tabs");

tabs.activeTab.attach({
contentScript:
'document.body.style.border="5px solid black";' +
'postMessage(document.getElementById("#my-watched-element").innerText);',
onMessage: function (data) {
// data is equal to the text of my DOM element with ID "#my-watched-element"
}
});

@param options {object}
Options for the page mod, with the following keys:

@prop [contentScriptFile] {string,array}
The local file URLs of content scripts to load. Content scripts specified
by this option are loaded *before* those specified by the `contentScript`
option. Optional.
@prop [contentScript] {string,array}
The texts of content scripts to load. Content scripts specified by this
option are loaded *after* those specified by the `contentScriptFile` option.
Optional.
@prop [onMessage] {function}
A function called when the page mod receives a message from content scripts.
Listeners are passed a single argument, the message posted from the
content script.
</api>

</api>
9 changes: 3 additions & 6 deletions packages/addon-kit/lib/context-menu.js
Expand Up @@ -423,7 +423,8 @@ let browserManager = {

// A type of Worker tailored to our uses.
const ContextMenuWorker = Worker.compose({

destroy: Worker.required,

// Returns true if any context listeners are defined in the worker's port.
anyContextListeners: function CMW_anyContextListeners() {
return this._port._listeners("context").length > 0;
Expand All @@ -444,12 +445,8 @@ const ContextMenuWorker = Worker.compose({
// clicked.
fireClick: function CMW_fireClick(popupNode, clickedItemData) {
this._port._emit("click", popupNode, clickedItemData);
},

// Frees the worker's resources.
destroy: function CMW_destroy() {
this._deconstructWorker();
}

});


Expand Down
18 changes: 5 additions & 13 deletions packages/addon-kit/lib/page-worker.js
Expand Up @@ -44,9 +44,6 @@
const { Symbiont } = require("content");
const { Trait } = require("traits");

const ERR_DESTROYED =
"The page has been destroyed and can no longer be used.";

if (!require("xul-app").isOneOf(["Firefox", "Thunderbird"])) {
throw new Error([
"The page-worker module currently supports only Firefox and Thunderbird. ",
Expand All @@ -58,12 +55,14 @@ if (!require("xul-app").isOneOf(["Firefox", "Thunderbird"])) {

const Page = Trait.compose(
Symbiont.resolve({
constructor: '_initSymbiont',
postMessage: '_postMessage'
constructor: '_initSymbiont'
}),
{
_frame: Trait.required,
_initFrame: Trait.required,
postMessage: Symbiont.required,
on: Symbiont.required,
destroy: Symbiont.required,

constructor: function Page(options) {
options = options || {};
Expand All @@ -87,14 +86,7 @@ const Page = Trait.compose(

this._initSymbiont();
},
postMessage: function postMessage(message) {
if (!this._frame)
throw new Error(ERR_DESTROYED);
this._postMessage(message);
},
destroy: function destroy() {
this._destructor();
},

_onChange: function _onChange(e) {
if ('contentURL' in e)
this._initFrame(this._frame);
Expand Down
2 changes: 1 addition & 1 deletion packages/addon-kit/lib/panel.js
Expand Up @@ -67,7 +67,7 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
const Panel = Symbiont.resolve({
constructor: '_init',
_onInit: '_onSymbiontInit',
_destructor: '_symbiontDestructor'
destroy: '_symbiontDestructor'
}).compose({
_frame: Symbiont.required,
_init: Symbiont.required,
Expand Down
114 changes: 114 additions & 0 deletions packages/addon-kit/tests/test-tabs.js
Expand Up @@ -546,6 +546,120 @@ exports.testPerTabEvents = function(test) {
});
};

exports.testAttachOnOpen = function (test) {
// Take care that attach has to be called on tab ready and not on tab open.
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let tabs = require("tabs");

tabs.open({
url: "data:text/html,foobar",
onOpen: function (tab) {
let worker = tab.attach({
contentScript: 'postMessage(document.location.href); ',
onMessage: function (msg) {
test.assertEqual(msg, "about:blank",
"Worker document url is about:blank on open");
worker.destroy();
closeBrowserWindow(window, function() test.done());
}
});
}
});

});
}

exports.testAttachAll = function (test) {
// Example of attach that process all tab documents
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let tabs = require("tabs");
let firstLocation = "data:text/html,foobar";
let secondLocation = "data:text/html,bar";
let thirdLocation = "data:text/html,fox";
let onMessageCount = 0;
let onReadyCount = 0;

let worker = null;
tabs.open({
url: firstLocation,
onReady: function (tab) {
onReadyCount++;
if (onReadyCount==1) {
worker = tab.attach({
contentScript: 'self.on("message", ' +
' function () postMessage(document.location.href)' +
');',
onMessage: function (msg) {
console.log("onmessage...");
onMessageCount++;
if (onMessageCount==1) {
test.assertEqual(msg, firstLocation,
"Worker document url is equal to the 1st document");
tab.url = secondLocation;
}
else if (onMessageCount==2) {
test.assertEqual(msg, secondLocation,
"Worker document url is equal to the 2nd document");
worker.destroy();
tab.url = thirdLocation;
} else {
test.fail("Got unexpected message ("+onMessageCount+")");
}
}
});
worker.postMessage("new-doc");
}
else if (onReadyCount==2) {
worker.postMessage("new-doc");
}
else if (onReadyCount==3) {
test.assertRaises(function () {
worker.postMessage("new-doc");
},
/The page has been destroyed/,
"Last postMessage throw because worker is destroyed");
closeBrowserWindow(window, function() test.done());
}

}
});

});
}


exports.testAttachWrappers = function (test) {
// Check that content script has access to unwrapped values
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let tabs = require("tabs");
let document = "data:text/html,<script>var globalJSVar=true;</script>";
let count = 0;

tabs.open({
url: document,
onReady: function (tab) {
let worker = tab.attach({
contentScript: 'try {' +
' postMessage(globalJSVar);' +
' postMessage(window.globalJSVar);' +
'} catch(e) {' +
' postMessage(e.message);' +
'}',
onMessage: function (msg) {
test.assertEqual(msg, true, "Worker has access to javascript content globals ("+count+")");
if (count++==1)
closeBrowserWindow(window, function() test.done());
}
});
}
});

});
}

/******************* helpers *********************/

// Helper for getting the active window
Expand Down
12 changes: 9 additions & 3 deletions packages/api-utils/lib/content/symbiont.js
Expand Up @@ -58,9 +58,13 @@ const ON_READY = 'DOMContentLoaded';
* that will be loaded in the provided frame, if frame is not provided
* Worker will create hidden one.
*/
const Symbiont = Worker.resolve({ constructor: '_onInit' }).compose({
const Symbiont = Worker.resolve({
constructor: '_onInit',
destroy: '_workerDestroy'
}).compose({
_window: Worker.required,
_onInit: Worker.required,

/**
* The constructor requires all the options that are required by
* `require('content').Worker` with the difference that the `frame` option
Expand Down Expand Up @@ -101,9 +105,9 @@ const Symbiont = Worker.resolve({ constructor: '_onInit' }).compose({
}));
}

unload.when(this._destructor.bind(this));
unload.when(this.destroy.bind(this));
},
_destructor: function _destructor() {
destroy: function destroy() {
// The frame might not have been initialized yet.
if (!this._frame)
return;
Expand All @@ -114,6 +118,8 @@ const Symbiont = Worker.resolve({ constructor: '_onInit' }).compose({
observers.remove(ON_START, this._onStart);

this._frame = null;

this._workerDestroy();
},
/**
* XUL iframe or browser elements with attribute `type` being `content`.
Expand Down
13 changes: 9 additions & 4 deletions packages/api-utils/lib/content/worker.js
Expand Up @@ -49,6 +49,9 @@ const unload = require('unload');

const JS_VERSION = '1.8';

const ERR_DESTROYED =
"The page has been destroyed and can no longer be used.";

/**
* Extended `EventEmitter` allowing us to emit events asynchronously.
*/
Expand All @@ -71,9 +74,11 @@ const AsyncEventEmitter = EventEmitter.compose({
* _Later this will be sending data across process boundaries._
* @param {JSON|String|Number|Boolean} data
*/
function postMessage(data)
function postMessage(data) {
if (!this._port)
throw new Error(ERR_DESTROYED);
this._port._asyncEmit('message', JSON.parse(JSON.stringify(data)));

}

/**
* Local trait providing implementation of the workers global scope.
Expand Down Expand Up @@ -329,7 +334,7 @@ const Worker = AsyncEventEmitter.compose({
if ('onMessage' in options)
this.on('message', options.onMessage);

unload.when(this._deconstructWorker.bind(this));
unload.when(this.destroy.bind(this));

WorkerGlobalScope(this); // will set this._port pointing to the private API
},
Expand All @@ -341,7 +346,7 @@ const Worker = AsyncEventEmitter.compose({
/**
* Tells _port to unload itself and removes all the references from itself.
*/
_deconstructWorker: function _deconstructWorker() {
destroy: function destroy() {
this._removeAllListeners('message');
this._removeAllListeners('error');
if (this._port) // maybe unloaded before port is created
Expand Down
11 changes: 11 additions & 0 deletions packages/api-utils/lib/tabs/tab.js
Expand Up @@ -186,6 +186,17 @@ const TabTrait = Trait.compose(EventEmitter, {
unpin: function unpin() {
this._window.gBrowser.unpinTab(this._tab);
},

/**
* Create a worker for this tab, first argument is options given to Worker.
* @type {Worker}
*/
attach: function attach(options) {
let { Worker } = require("content/worker");
options.window = this._contentWindow.wrappedJSObject;
return Worker(options);
},

/**
* Make this tab active.
* Please note: That this function is called synchronous since in E10S that
Expand Down

0 comments on commit ab41edc

Please sign in to comment.