This repository has been archived by the owner on Feb 26, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #903 from Gozala/bug/tab-detraitifaction@854980
Bug 854980 - Implementing simple utils for observing window / tab events r=@Mossop
- Loading branch information
Showing
12 changed files
with
910 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.