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

Commit

Permalink
Merge pull request #903 from Gozala/bug/tab-detraitifaction@854980
Browse files Browse the repository at this point in the history
Bug 854980 - Implementing simple utils for observing window / tab events r=@Mossop
  • Loading branch information
Gozala committed Apr 1, 2013
2 parents d12cec8 + c35cbd7 commit e5fd0a0
Show file tree
Hide file tree
Showing 12 changed files with 910 additions and 0 deletions.
20 changes: 20 additions & 0 deletions lib/sdk/browser/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* 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";

module.metadata = {
"stability": "unstable"
};

const { events } = require("../window/events");
const { filter } = require("../event/utils");
const { isBrowser } = require("../window/utils");

// TODO: `isBrowser` detects weather window is a browser by checking
// `windowtype` attribute, which means that all 'open' events will be
// filtered out since document is not loaded yet. Maybe we can find a better
// implementation for `isBrowser`. Either way it's not really needed yet
// neither window tracker provides this event.

exports.events = filter(function({target}) isBrowser(target), events);
26 changes: 26 additions & 0 deletions lib/sdk/event/dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* 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";

module.metadata = {
"stability": "unstable"
};

let { emit, on, off } = require("./core");

// Simple utility function takes event target, event type and optional
// `options.capture` and returns node style event stream that emits "data"
// events every time event of that type occurs on the given `target`.
function open(target, type, options) {
let output = {};
let capture = options && options.capture ? true : false;

target.addEventListener(type, function(event) {
emit(output, "data", event);
}, capture);

return output;
}
exports.open = open;
101 changes: 101 additions & 0 deletions lib/sdk/event/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* 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";

module.metadata = {
"stability": "unstable"
};

let { emit, on, off } = require("./core");

// This module provides set of high order function for working with event
// streams (streams in a NodeJS style that dispatch data, end and error
// events).

// Function takes a `target` object and returns set of implicit references
// (non property references) it keeps. This basically allows defining
// references between objects without storing the explicitly. See transform for
// more details.
let refs = (function() {
let refSets = new WeakMap();
return function refs(target) {
if (!refSets.has(target)) refSets.set(target, new Set());
return refSets.get(target);
}
})();

function transform(f, input) {
let output = {};

// Since event listeners don't prevent `input` to be GC-ed we wanna presrve
// it until `output` can be GC-ed. There for we add implicit reference which
// is removed once `input` ends.
refs(output).add(input);

function next(data) emit(output, "data", data);
on(input, "error", function(error) emit(output, "error", error));
on(input, "end", function() {
refs(output).delete(input);
emit(output, "end");
});
on(input, "data", function(data) f(data, next));
return output;
}
// High order event transformation function that takes `input` event channel
// and returns transformation containing only events on which `p` predicate
// returns `true`.
function filter(predicate, input) {
return transform(function(data, next) {
if (predicate(data)) next(data)
}, input);
}
exports.filter = filter;
// High order function that takes `input` and returns input of it's values
// mapped via given `f` function.
function map(f, input) transform(function(data, next) next(f(data)), input)
exports.map = map;
// High order function that takes `input` stream of streams and merges them
// into single event stream. Like flatten but time based rather than order
// based.
function merge(inputs) {
let output = {};
let open = 1;
let state = [];
output.state = state;
refs(output).add(inputs);
function end(input) {
open = open - 1;
refs(output).delete(input);
if (open === 0) emit(output, "end");
}
function error(e) emit(output, "error", e);
function forward(input) {
state.push(input);
open = open + 1;
on(input, "end", function() end(input));
on(input, "error", error);
on(input, "data", function(data) emit(output, "data", data));
}
// If `inputs` is an array treat it as a stream.
if (Array.isArray(inputs)) {
inputs.forEach(forward)
end(inputs)
}
else {
on(inputs, "end", function() end(inputs));
on(inputs, "error", error);
on(inputs, "data", forward);
}

return output;
}
exports.merge = merge;

function expand(f, inputs) merge(map(f, inputs))
exports.expand = expand;
60 changes: 60 additions & 0 deletions lib/sdk/tab/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* 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";

// This module provides temporary shim until Bug 843901 is shipped.
// It basically registers tab event listeners on all windows that get
// opened and forwards them through observer notifications.

module.metadata = {
"stability": "experimental"
};

const { Ci } = require("chrome");
const { windows, isInteractive } = require("../window/utils");
const { events } = require("../browser/events");
const { open } = require("../event/dom");
const { filter, map, merge, expand } = require("../event/utils");

// Module provides event stream (in nodejs style) that emits data events
// for all the tab events that happen in running firefox. At the moment
// it does it by registering listeners on all browser windows and then
// forwarding events when they occur to a stream. This will become obsolete
// once Bug 843901 is fixed, and we'll just leverage observer notifications.

// Set of tab events that this module going to aggregate and expose.
const TYPES = ["TabOpen","TabClose","TabSelect","TabMove","TabPinned",
"TabUnpinned"];

// Utility function that given a browser `window` returns stream of above
// defined tab events for all tabs on the given window.
function tabEventsFor(window) {
// Map supported event types to a streams of those events on the given
// `window` and than merge these streams into single form stream off
// all events.
let channels = TYPES.map(function(type) open(window, type));
return merge(channels);
}

// Filter DOMContentLoaded events from all the browser events.
let readyEvents = filter(function(e) e.type === "DOMContentLoaded", events);
// Map DOMContentLoaded events to it's target browser windows.
let futureWindows = map(function(e) e.target, readyEvents);
// Expand all browsers that will become interactive to supported tab events
// on these windows. Result will be a tab events from all tabs of all windows
// that will become interactive.
let eventsFromFuture = expand(tabEventsFor, futureWindows);

// Above covers only windows that will become interactive in a future, but some
// windows may already be interactive so we pick those and expand to supported
// tab events for them too.
let interactiveWindows = windows("navigator:browser", { includePrivate: true }).
filter(isInteractive);
let eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));


// Finally merge stream of tab events from future windows and current windows
// to cover all tab events on all windows that will open.
exports.events = merge([eventsFromInteractive, eventsFromFuture]);
50 changes: 50 additions & 0 deletions lib/sdk/tabs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,53 @@ function getTabForBrowser(browser) {
}
exports.getTabForBrowser = getTabForBrowser;

function pin(tab) {
let gBrowser = getTabBrowserForTab(tab);
// TODO: Implement Fennec support
if (gBrowser) gBrowser.pinTab(tab);
}
exports.pin = pin;

function unpin(tab) {
let gBrowser = getTabBrowserForTab(tab);
// TODO: Implement Fennec support
if (gBrowser) gBrowser.unpinTab(tab);
}
exports.unpin = unpin;

function isPinned(tab) !!tab.pinned
exports.isPinned = isPinned;

function reload(tab) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) gBrowser.unpinTab(tab);
// Fennec
else if (tab.browser) tab.browser.reload();
}
exports.reload = reload

function getIndex(tab) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) {
let document = getBrowserForTab(tab).contentDocument;
return gBrowser.getBrowserIndexForDocument(document);
}
// Fennec
else {
let window = getWindowHoldingTab(tab)
let tabs = window.BrowserApp.tabs;
for (let i = tabs.length; i >= 0; i--)
if (tabs[i] === tab) return i;
}
}
exports.getIndex = getIndex;

function move(tab, index) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) gBrowser.moveTabTo(tab, index);
// TODO: Implement fennec support
}
exports.move = move;
80 changes: 80 additions & 0 deletions lib/sdk/window/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* 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";

module.metadata = {
"stability": "unstable"
};

const { Ci } = require("chrome");
const events = require("../system/events");
const { on, off, emit } = require("../event/core");
const { windows } = require("../window/utils");

// Object represents event channel on which all top level window events
// will be dispatched, allowing users to react to those evens.
const channel = {};
exports.events = channel;

const types = {
domwindowopened: "open",
domwindowclosed: "close",
}

// Utility function to query observer notification subject to get DOM window.
function nsIDOMWindow($) $.QueryInterface(Ci.nsIDOMWindow);

// Utility function used as system event listener that is invoked every time
// top level window is open. This function does two things:
// 1. Registers event listeners to track when document becomes interactive and
// when it's done loading. This will become obsolete once Bug 843910 is
// fixed.
// 2. Forwards event to an exported event stream.
function onOpen(event) {
observe(nsIDOMWindow(event.subject));
dispatch(event);
}

// Function registers single shot event listeners for relevant window events
// that forward events to exported event stream.
function observe(window) {
function listener(event) {
if (event.target === window.document) {
window.removeEventListener(event.type, listener, true);
emit(channel, "data", { type: event.type, target: window });
}
}

// Note: we do not remove listeners on unload since on add-on unload we
// nuke add-on sandbox that should allow GC-ing listeners. This also has
// positive effects on add-on / firefox unloads.
window.addEventListener("DOMContentLoaded", listener, true);
window.addEventListener("load", listener, true);
// TODO: Also add focus event listener so that can be forwarded to event
// stream. It can be part of Bug 854982.
}

// Utility function that takes system notification event and forwards it to a
// channel in restructured form.
function dispatch({ type: topic, subject }) {
emit(channel, "data", {
topic: topic,
type: types[topic],
target: nsIDOMWindow(subject)
});
}

// In addition to observing windows that are open we also observe windows
// that are already already opened in case they're in process of loading.
let opened = windows(null, { includePrivate: true });
opened.forEach(observe);

// Register system event listeners to forward messages on exported event
// stream. Note that by default only weak refs are kept by system events
// module so they will be GC-ed once add-on unloads and no manual cleanup
// is required. Also note that listeners are intentionally not inlined since
// to avoid premature GC-ing. Currently refs are kept by module scope and there
// for they remain alive.
events.on("domwindowopened", onOpen);
events.on("domwindowclosed", dispatch);
9 changes: 9 additions & 0 deletions lib/sdk/window/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ function windows(type, options) {
}
exports.windows = windows;

/**
* Check if the given window is interactive.
* i.e. if its "DOMContentLoaded" event has already been fired.
* @params {nsIDOMWindow} window
*/
function isInteractive(window)
window.document.readyState === "interactive" || isDocumentLoaded(window)
exports.isInteractive = isInteractive;

/**
* Check if the given window is completely loaded.
* i.e. if its "load" event has already been fired and all possible DOM content
Expand Down
Loading

0 comments on commit e5fd0a0

Please sign in to comment.