Skip to content

Commit

Permalink
Bug 993272 - Uplift Add-on SDK to Firefox
Browse files Browse the repository at this point in the history
  • Loading branch information
erikvold committed Apr 15, 2014
1 parent 37d1ad7 commit c9e0def
Show file tree
Hide file tree
Showing 24 changed files with 440 additions and 51 deletions.
29 changes: 19 additions & 10 deletions addon-sdk/source/examples/reading-data/lib/main.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

var self = require("sdk/self");
var panels = require("sdk/panel");
var widgets = require("sdk/widget");
var { Panel } = require("sdk/panel");
var { ToggleButton } = require("sdk/ui");

function replaceMom(html) {
return html.replace("World", "Mom");
Expand All @@ -21,24 +22,32 @@ exports.main = function(options, callbacks) {
helloHTML = replaceMom(helloHTML);

// ... and then create a panel that displays it.
var myPanel = panels.Panel({
contentURL: "data:text/html," + helloHTML
var myPanel = Panel({
contentURL: "data:text/html," + helloHTML,
onHide: handleHide
});

// Load the URL of the sample image.
var iconURL = self.data.url("mom.png");

// Create a widget that displays the image. We'll attach the panel to it.
// When you click the widget, the panel will pop up.
widgets.Widget({
var button = ToggleButton({
id: "test-widget",
label: "Mom",
contentURL: iconURL,
panel: myPanel
icon: './mom.png',
onChange: handleChange
});

// If you run cfx with --static-args='{"quitWhenDone":true}' this program
// will automatically quit Firefox when it's done.
if (options.staticArgs.quitWhenDone)
callbacks.quit();
}

function handleChange(state) {
if (state.checked) {
myPanel.show({ position: button });
}
}

function handleHide() {
button.state('window', { checked: false });
}
4 changes: 0 additions & 4 deletions addon-sdk/source/examples/reading-data/tests/test-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// Disable tests below for now.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=987348
/*
var m = require("main");
var self = require("sdk/self");

Expand All @@ -26,4 +23,3 @@ exports.testID = function(test) {
test.assertEqual(self.data.url("sample.html"),
"resource://reading-data-example-at-jetpack-dot-mozillalabs-dot-com/reading-data/data/sample.html");
};
*/
28 changes: 23 additions & 5 deletions addon-sdk/source/examples/reddit-panel/lib/main.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

var data = require("sdk/self").data;
var { data } = require("sdk/self");
var { ToggleButton } = require("sdk/ui");

var base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
"AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
"N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
"bWRR9AAAAABJRU5ErkJggg%3D%3D";

var reddit_panel = require("sdk/panel").Panel({
width: 240,
height: 320,
contentURL: "http://www.reddit.com/.mobile?keep_extension=True",
contentScriptFile: [data.url("jquery-1.4.4.min.js"),
data.url("panel.js")]
data.url("panel.js")],
onHide: handleHide
});

reddit_panel.port.on("click", function(url) {
require("sdk/tabs").open(url);
});

require("sdk/widget").Widget({
let button = ToggleButton({
id: "open-reddit-btn",
label: "Reddit",
contentURL: "http://www.reddit.com/static/favicon.ico",
panel: reddit_panel
icon: base64png,
onChange: handleChange
});

exports.main = function(options, callbacks) {
Expand All @@ -29,3 +37,13 @@ exports.main = function(options, callbacks) {
if (options.staticArgs.quitWhenDone)
callbacks.quit();
};

function handleChange(state) {
if (state.checked) {
reddit_panel.show({ position: button });
}
}

function handleHide() {
button.state('window', { checked: false });
}
4 changes: 0 additions & 4 deletions addon-sdk/source/examples/reddit-panel/tests/test-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// Disable tests below for now.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=987348
/*
var m = require("main");
var self = require("sdk/self");

Expand All @@ -23,4 +20,3 @@ exports.testMain = function(test) {
exports.testData = function(test) {
test.assert(self.data.load("panel.js").length > 0);
};
*/
4 changes: 3 additions & 1 deletion addon-sdk/source/lib/sdk/content/content-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ const ContentWorker = Object.freeze({
error: pipe.emit.bind(null, "console", "error"),
debug: pipe.emit.bind(null, "console", "debug"),
exception: pipe.emit.bind(null, "console", "exception"),
trace: pipe.emit.bind(null, "console", "trace")
trace: pipe.emit.bind(null, "console", "trace"),
time: pipe.emit.bind(null, "console", "time"),
timeEnd: pipe.emit.bind(null, "console", "timeEnd")
});
},

Expand Down
6 changes: 6 additions & 0 deletions addon-sdk/source/lib/sdk/content/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ let detachFrom = method("detatchFrom");
exports.detachFrom = detachFrom;

function attach(modification, target) {
if (!modification)
return;

let window = getTargetWindow(target);

attachTo(modification, window);
Expand All @@ -42,6 +45,9 @@ function attach(modification, target) {
exports.attach = attach;

function detach(modification, target) {
if (!modification)
return;

if (target) {
let window = getTargetWindow(target);
detachFrom(modification, window);
Expand Down
5 changes: 5 additions & 0 deletions addon-sdk/source/lib/sdk/loader/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,8 @@ function load(sandbox, uri) {
}
}
exports.load = load;

/**
* Forces the given `sandbox` to be freed immediately.
*/
exports.nuke = Cu.nukeSandbox
4 changes: 4 additions & 0 deletions addon-sdk/source/lib/sdk/page-mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ function createWorker (mod, window) {
contentScript: mod.contentScript,
contentScriptFile: mod.contentScriptFile,
contentScriptOptions: mod.contentScriptOptions,
// Bug 980468: Syntax errors from scripts can happen before the worker
// can set up an error handler. They are per-mod rather than per-worker
// so are best handled at the mod level.
onError: (e) => emit(mod, 'error', e)
});
workers.set(mod, worker);
pipe(worker, mod);
Expand Down
56 changes: 44 additions & 12 deletions addon-sdk/source/lib/sdk/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const { isPrivateBrowsingSupported } = require('./self');
const { isWindowPBSupported } = require('./private-browsing/utils');
const { Class } = require("./core/heritage");
const { merge } = require("./util/object");
const { WorkerHost, detach, attach, destroy } = require("./content/utils");
const { WorkerHost } = require("./content/utils");
const { Worker } = require("./content/worker");
const { Disposable } = require("./core/disposable");
const { WeakReference } = require('./core/reference');
Expand All @@ -34,6 +34,8 @@ const { getNodeView, getActiveView } = require("./view/core");
const { isNil, isObject, isNumber } = require("./lang/type");
const { getAttachEventType } = require("./content/utils");
const { number, boolean, object } = require('./deprecated/api-utils');
const { Style } = require("./stylesheet/style");
const { attach, detach } = require("./content/mod");

let isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
some(value => isNumber(value) && !isNaN(value));
Expand Down Expand Up @@ -63,7 +65,16 @@ let displayContract = contract({
position: position
});

let panelContract = contract(merge({}, displayContract.rules, loaderContract.rules));
let panelContract = contract(merge({
// contentStyle* / contentScript* are sharing the same validation constraints,
// so they can be mostly reused, except for the messages.
contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
msg: 'The `contentStyle` option must be a string or an array of strings.'
}),
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
})
}, displayContract.rules, loaderContract.rules));


function isDisposed(panel) !views.has(panel);
Expand All @@ -72,12 +83,13 @@ let panels = new WeakMap();
let models = new WeakMap();
let views = new WeakMap();
let workers = new WeakMap();
let styles = new WeakMap();

function viewFor(panel) views.get(panel)
function modelFor(panel) models.get(panel)
function panelFor(view) panels.get(view)
function workerFor(panel) workers.get(panel)

const viewFor = (panel) => views.get(panel);
const modelFor = (panel) => models.get(panel);
const panelFor = (view) => panels.get(view);
const workerFor = (panel) => workers.get(panel);
const styleFor = (panel) => styles.get(panel);

// Utility function takes `panel` instance and makes sure it will be
// automatically hidden as soon as other panel is shown.
Expand Down Expand Up @@ -125,6 +137,12 @@ const Panel = Class({
}, panelContract(options));
models.set(this, model);

if (model.contentStyle || model.contentStyleFile) {
styles.set(this, Style({
uri: model.contentStyleFile,
source: model.contentStyle
}));
}

// Setup view
let view = domPanel.make();
Expand All @@ -148,7 +166,8 @@ const Panel = Class({
this.hide();
off(this);

destroy(workerFor(this));
workerFor(this).destroy();
detach(styleFor(this));

domPanel.dispose(viewFor(this));

Expand Down Expand Up @@ -177,7 +196,7 @@ const Panel = Class({
domPanel.setURL(viewFor(this), model.contentURL);
// Detach worker so that messages send will be queued until it's
// reatached once panel content is ready.
detach(workerFor(this));
workerFor(this).detach();
},

/* Public API: Panel.isShowing */
Expand Down Expand Up @@ -262,12 +281,25 @@ let hides = filter(panelEvents, ({type}) => type === "popuphidden");
let ready = filter(panelEvents, ({type, target}) =>
getAttachEventType(modelFor(panelFor(target))) === type);

// Styles should be always added as soon as possible, and doesn't makes them
// depends on `contentScriptWhen`
let start = filter(panelEvents, ({type}) => type === "document-element-inserted");

// Forward panel show / hide events to panel's own event listeners.
on(shows, "data", ({target}) => emit(panelFor(target), "show"));

on(hides, "data", ({target}) => emit(panelFor(target), "hide"));

on(ready, "data", function({target}) {
let worker = workerFor(panelFor(target));
attach(worker, domPanel.getContentDocument(target).defaultView);
on(ready, "data", ({target}) => {
let panel = panelFor(target);
let window = domPanel.getContentDocument(target).defaultView;

workerFor(panel).attach(window);
});

on(start, "data", ({target}) => {
let panel = panelFor(target);
let window = domPanel.getContentDocument(target).defaultView;

attach(styleFor(panel), window);
});
8 changes: 6 additions & 2 deletions addon-sdk/source/lib/sdk/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,21 @@ exports.pathFor = function pathFor(id) {
*/
exports.platform = runtime.OS.toLowerCase();

const [, architecture, compiler] = runtime.XPCOMABI ?
runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) :
[, null, null];

/**
* What processor architecture you're running on:
* `'arm', 'ia32', or 'x64'`.
*/
exports.architecture = runtime.XPCOMABI.split('_')[0];
exports.architecture = architecture;

/**
* What compiler used for build:
* `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
*/
exports.compiler = runtime.XPCOMABI.split('_')[1];
exports.compiler = compiler;

/**
* The application's build ID/date, for example "2004051604".
Expand Down
32 changes: 31 additions & 1 deletion addon-sdk/source/lib/sdk/system/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ module.metadata = {
'stability': 'unstable'
};

const { Cc, Ci } = require('chrome');
const { Cc, Ci, Cu } = require('chrome');
const { Unknown } = require('../platform/xpcom');
const { Class } = require('../core/heritage');
const { ns } = require('../core/namespace');
const { addObserver, removeObserver, notifyObservers } =
Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
const unloadSubject = require('@loader/unload');

const Subject = Class({
extends: Unknown,
Expand Down Expand Up @@ -94,6 +95,10 @@ function on(type, listener, strong) {
let observer = Observer(listener);
observers[type] = observer;
addObserver(observer, type, weak);
// WeakRef gymnastics to remove all alive observers on unload
let ref = Cu.getWeakReference(observer);
weakRefs.set(observer, ref);
stillAlive.set(ref, type);
}
}
exports.on = on;
Expand All @@ -120,6 +125,31 @@ function off(type, listener) {
let observer = observers[type];
delete observers[type];
removeObserver(observer, type);
stillAlive.delete(weakRefs.get(observer));
}
}
exports.off = off;

// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115
let weakRefs = new WeakMap();

// and we're out of beta, we're releasing on time!
let stillAlive = new Map();

on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
// using logic from ./unload, to avoid a circular module reference
if (subject.wrappedJSObject === unloadSubject) {
off('sdk:loader:destroy', onunload);

// don't bother
if (reason === 'shutdown')
return;

stillAlive.forEach( (type, ref) => {
let observer = ref.get();
if (observer)
removeObserver(observer, type);
})
}
// a strong reference
}, true);
Loading

0 comments on commit c9e0def

Please sign in to comment.