Bug 1196975 - part2: shimwaiver applications. r=mossop

This commit is contained in:
Gabor Krizsanits 2015-10-09 06:00:00 +02:00
parent b42691a41d
commit 2499c8a6bf
15 changed files with 153 additions and 128 deletions

View File

@ -282,6 +282,7 @@ EXTRA_JS_MODULES.commonjs.sdk.deprecated.events += [
]
EXTRA_JS_MODULES.commonjs.sdk.dom += [
'source/lib/sdk/dom/events-shimmed.js',
'source/lib/sdk/dom/events.js',
]
@ -408,6 +409,7 @@ EXTRA_JS_MODULES.commonjs.sdk.stylesheet += [
EXTRA_JS_MODULES.commonjs.sdk.system += [
'source/lib/sdk/system/child_process.js',
'source/lib/sdk/system/environment.js',
'source/lib/sdk/system/events-shimmed.js',
'source/lib/sdk/system/events.js',
'source/lib/sdk/system/globals.js',
'source/lib/sdk/system/process.js',

View File

@ -9,14 +9,17 @@ module.metadata = {
};
const { Cc, Ci, Cr } = require("chrome");
const { Cc, Ci, Cr, Cu } = require("chrome");
const { Class } = require("./heritage");
const { isWeak } = require("./reference");
const method = require("../../method/core");
const { addObserver, removeObserver } = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const observerService = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
// This is a method that will be invoked when notification observer
// subscribed to occurs.

View File

@ -0,0 +1,18 @@
/* 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('./events.js');
exports.emit = (element, type, obj) => events.emit(element, type, obj, true);
exports.on = (element, type, listener, capture) => events.on(element, type, listener, capture, true);
exports.once = (element, type, listener, capture) => events.once(element, type, listener, capture, true);
exports.removeListener = (element, type, listener, capture) => events.removeListener(element, type, listener, capture, true);
exports.removed = events.removed;
exports.when = (element, eventName, capture) => events.when(element, eventName, capture ? capture : false, true);

View File

@ -8,6 +8,9 @@ module.metadata = {
"stability": "unstable"
};
const { Cu } = require("chrome");
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
// Utility function that returns copy of the given `text` with last character
// removed if it is `"s"`.
function singularify(text) {
@ -44,10 +47,14 @@ function getInitializerName(category) {
* See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
* for a detailed explanation.
*/
function on(element, type, listener, capture) {
function on(element, type, listener, capture, shimmed = false) {
// `capture` defaults to `false`.
capture = capture || false;
element.addEventListener(type, listener, capture);
if (shimmed) {
element.addEventListener(type, listener, capture);
} else {
ShimWaiver.getProperty(element, "addEventListener")(type, listener, capture);
}
}
exports.on = on;
@ -73,11 +80,11 @@ exports.on = on;
* See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
* for a detailed explanation.
*/
function once(element, type, listener, capture) {
function once(element, type, listener, capture, shimmed = false) {
on(element, type, function selfRemovableListener(event) {
removeListener(element, type, selfRemovableListener, capture);
removeListener(element, type, selfRemovableListener, capture, shimmed);
listener.apply(this, arguments);
}, capture);
}, capture, shimmed);
}
exports.once = once;
@ -103,8 +110,12 @@ exports.once = once;
* See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
* for a detailed explanation.
*/
function removeListener(element, type, listener, capture) {
element.removeEventListener(type, listener, capture);
function removeListener(element, type, listener, capture, shimmed = false) {
if (shimmed) {
element.removeEventListener(type, listener, capture);
} else {
ShimWaiver.getProperty(element, "removeEventListener")(type, listener, capture);
}
}
exports.removeListener = removeListener;
@ -128,13 +139,17 @@ exports.removeListener = removeListener;
* initializer after firs `type` argument.
* @see https://developer.mozilla.org/En/DOM/Document.createEvent
*/
function emit(element, type, { category, initializer, settings }) {
function emit(element, type, { category, initializer, settings }, shimmed = false) {
category = category || "UIEvents";
initializer = initializer || getInitializerName(category);
let document = element.ownerDocument;
let event = document.createEvent(category);
event[initializer].apply(event, [type].concat(settings));
element.dispatchEvent(event);
if (shimmed) {
element.dispatchEvent(event);
} else {
ShimWaiver.getProperty(element, "dispatchEvent")(event);
}
};
exports.emit = emit;
@ -158,12 +173,20 @@ const removed = element => {
};
exports.removed = removed;
const when = (element, eventName, capture=false) => new Promise(resolve => {
const when = (element, eventName, capture=false, shimmed=false) => new Promise(resolve => {
const listener = event => {
element.removeEventListener(eventName, listener, capture);
if (shimmed) {
element.removeEventListener(eventName, listener, capture);
} else {
ShimWaiver.getProperty(element, "removeEventListener")(eventName, listener, capture);
}
resolve(event);
};
element.addEventListener(eventName, listener, capture);
if (shimmed) {
element.addEventListener(eventName, listener, capture);
} else {
ShimWaiver.getProperty(element, "addEventListener")(eventName, listener, capture);
}
});
exports.when = when;

View File

@ -8,10 +8,15 @@ module.metadata = {
"stability": "unstable"
};
const { Cc, Ci, Cr } = require("chrome");
const { Cc, Ci, Cr, Cu } = require("chrome");
const { emit, on, off } = require("./core");
const { addObserver, removeObserver } = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
var observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
const { when: unload } = require("../system/unload");
// Simple class that can be used to instantiate event channel that

View File

@ -14,6 +14,9 @@ var { emit } = require("./core");
var { when: unload } = require("../system/unload");
var listeners = new Map();
const { Cu } = require("chrome");
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
var getWindowFrom = x =>
x instanceof Ci.nsIDOMWindow ? x :
x instanceof Ci.nsIDOMDocument ? x.defaultView :
@ -21,7 +24,7 @@ var getWindowFrom = x =>
null;
function removeFromListeners() {
this.removeEventListener("DOMWindowClose", removeFromListeners);
ShimWaiver.getProperty(this, "removeEventListener")("DOMWindowClose", removeFromListeners);
for (let cleaner of listeners.get(this))
cleaner();
@ -56,11 +59,11 @@ function open(target, type, options) {
// We need to remove from our map the `window` once is closed, to prevent
// memory leak
window.addEventListener("DOMWindowClose", removeFromListeners);
ShimWaiver.getProperty(window, "addEventListener")("DOMWindowClose", removeFromListeners);
}
cleaners.push(() => target.removeEventListener(type, listener, capture));
target.addEventListener(type, listener, capture);
cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture));
ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture);
return output;
}

View File

@ -9,8 +9,12 @@ const { once, off } = require("../event/core");
const { id: addonID } = require("../self");
const unloadMessage = require("@loader/unload");
const { addObserver, removeObserver } = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const observerService = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
const addonUnloadTopic = "sdk:loader:destroy";

View File

@ -50,7 +50,8 @@ const Observer = Class({
* Keyboard event being emitted.
*/
handleEvent(event) {
emit(this, event.type, event, event.target.ownerDocument.defaultView);
emit(this, event.type, event, event.target.ownerDocument ? event.target.ownerDocument.defaultView
: undefined);
}
});

View File

@ -0,0 +1,16 @@
/* 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('./events.js');
exports.emit = (type, event) => events.emit(type, event, true);
exports.on = (type, listener, strong) => events.on(type, listener, strong, true);
exports.once = (type, listener) => events.once(type, listener, true);
exports.off = (type, listener) => events.off(type, listener, true);

View File

@ -12,8 +12,13 @@ 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 } =
const observerService =
Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
const { addObserver, removeObserver, notifyObservers } = observerService;
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserverNoShim = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserverNoShim = ShimWaiver.getProperty(observerService, "removeObserver");
const notifyObserversNoShim = ShimWaiver.getProperty(observerService, "notifyObservers");
const unloadSubject = require('@loader/unload');
const Subject = Class({
@ -33,7 +38,7 @@ const Subject = Class({
getInterfaces: function() {}
});
function emit(type, event) {
function emit(type, event, shimmed = false) {
// From bug 910599
// We must test to see if 'subject' or 'data' is a defined property
// of the event object, but also allow primitives to be passed in,
@ -48,7 +53,11 @@ function emit(type, event) {
// All other types return themselves (and cast to strings/null
// via observer service)
event;
notifyObservers(subject, type, data);
if (shimmed) {
notifyObservers(subject, type, data);
} else {
notifyObserversNoShim(subject, type, data);
}
}
exports.emit = emit;
@ -83,7 +92,7 @@ const Observer = Class({
const subscribers = ns();
function on(type, listener, strong) {
function on(type, listener, strong, shimmed = false) {
// Unless last optional argument is `true` we use a weak reference to a
// listener.
let weak = !strong;
@ -94,29 +103,34 @@ function on(type, listener, strong) {
if (!(type in observers)) {
let observer = Observer(listener);
observers[type] = observer;
addObserver(observer, type, weak);
if (shimmed) {
addObserver(observer, type, weak);
} else {
addObserverNoShim(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);
wasShimmed.set(ref, shimmed);
}
}
exports.on = on;
function once(type, listener) {
function once(type, listener, shimmed = false) {
// Note: this code assumes order in which listeners are called, which is fine
// as long as dispatch happens in same order as listener registration which
// is the case now. That being said we should be aware that this may break
// in a future if order will change.
on(type, listener);
on(type, listener, shimmed);
on(type, function cleanup() {
off(type, listener);
off(type, cleanup);
}, true);
off(type, listener, shimmed);
off(type, cleanup, shimmed);
}, true, shimmed);
}
exports.once = once;
function off(type, listener) {
function off(type, listener, shimmed = false) {
// Take list of observers as with the given `listener`.
let observers = subscribers(listener);
// If `observer` for the given `type` is registered, then
@ -124,8 +138,13 @@ function off(type, listener) {
if (type in observers) {
let observer = observers[type];
delete observers[type];
removeObserver(observer, type);
if (shimmed) {
removeObserver(observer, type);
} else {
removeObserverNoShim(observer, type);
}
stillAlive.delete(weakRefs.get(observer));
wasShimmed.delete(weakRefs.get(observer));
}
}
exports.off = off;
@ -134,12 +153,14 @@ exports.off = off;
var weakRefs = new WeakMap();
// and we're out of beta, we're releasing on time!
var stillAlive = new Map();
var stillAlive = new Map();
var wasShimmed = 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);
off('sdk:loader:destroy', onunload, false);
// don't bother
if (reason === 'shutdown')
@ -147,9 +168,14 @@ on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
stillAlive.forEach( (type, ref) => {
let observer = ref.get();
if (observer)
removeObserver(observer, type);
if (observer) {
if (wasShimmed.get(ref)) {
removeObserver(observer, type);
} else {
removeObserverNoShim(observer, type);
}
}
})
}
// a strong reference
}, true);
}, true, false);

View File

@ -17,7 +17,7 @@
"preferences-service": "sdk/preferences/service",
"promise": "sdk/core/promise",
"system": "sdk/system",
"system/events": "sdk/system/events",
"system/events": "sdk/system/events-shimmed",
"tabs/tab": "sdk/tabs/tab",
"tabs/utils": "sdk/tabs/utils",
"timer": "sdk/timers",
@ -40,7 +40,7 @@
"app-strings": "sdk/deprecated/app-strings",
"environment": "sdk/system/environment",
"keyboard/utils": "sdk/keyboard/utils",
"dom/events": "sdk/dom/events",
"dom/events": "sdk/dom/events-shimmed",
"utils/data": "sdk/io/data",
"test/assert": "sdk/test/assert",
"hidden-frame": "sdk/frame/hidden-frame",

View File

@ -142,7 +142,7 @@ exports["test compatibility"] = function(assert) {
require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils");
assert.equal(require("dom/events"),
require("sdk/dom/events"), "sdk/dom/events -> dom/events");
require("sdk/dom/events-shimmed"), "sdk/dom/events-shimmed -> dom/events");
assert.equal(require("tabs/tab.js"),
require("sdk/tabs/tab"), "sdk/tabs/tab -> tabs/tab.js");

View File

@ -42,86 +42,6 @@ var close = function(tab) {
return promise;
}
exports["test multiple tabs"] = function(assert, done) {
let loader = Loader(module);
let { events } = loader.require("sdk/content/events");
let { on, off } = loader.require("sdk/event/core");
let actual = [];
on(events, "data", handler);
function handler ({type, target, timeStamp}) {
eventFilter(type, target, () => {
actual.push(type + " -> " + target.URL)
});
}
let window = getMostRecentBrowserWindow();
let firstTab = open("data:text/html,first-tab", window);
when("pageshow", firstTab).
then(close).
then(use(window)).
then(open("data:text/html,second-tab")).
then(when("pageshow")).
then(close).
then(function() {
assert.deepEqual(actual, [
"document-element-inserted -> data:text/html,first-tab",
"DOMContentLoaded -> data:text/html,first-tab",
"load -> data:text/html,first-tab",
"pageshow -> data:text/html,first-tab",
"document-element-inserted -> data:text/html,second-tab",
"DOMContentLoaded -> data:text/html,second-tab",
"load -> data:text/html,second-tab",
"pageshow -> data:text/html,second-tab"
], "all events dispatche as expeced")
}, function(reason) {
assert.fail(Error(reason));
}).then(function() {
loader.unload();
off(events, "data", handler);
done();
});
};
exports["test nested frames"] = function(assert, done) {
let loader = Loader(module);
let { events } = loader.require("sdk/content/events");
let { on, off } = loader.require("sdk/event/core");
let actual = [];
on(events, "data", handler);
function handler ({type, target, timeStamp}) {
eventFilter(type, target, () => {
actual.push(type + " -> " + target.URL)
});
}
let window = getMostRecentBrowserWindow();
let uri = encodeURI("data:text/html,<iframe src='data:text/html,iframe'>");
let tab = open(uri, window);
when("pageshow", tab).
then(close).
then(function() {
assert.deepEqual(actual, [
"document-element-inserted -> " + uri,
"DOMContentLoaded -> " + uri,
"document-element-inserted -> data:text/html,iframe",
"DOMContentLoaded -> data:text/html,iframe",
"load -> data:text/html,iframe",
"pageshow -> data:text/html,iframe",
"load -> " + uri,
"pageshow -> " + uri
], "events where dispatched")
}, function(reason) {
assert.fail(Error(reason))
}).then(function() {
loader.unload();
off(events, "data", handler);
done();
});
};
exports["test dead object errors"] = function(assert, done) {
let system = require("sdk/system/events");
let loader = Loader(module);

View File

@ -14,7 +14,7 @@ const { Panel } = require("dev/panel");
const { Class } = require("sdk/core/heritage");
const { openToolbox, closeToolbox, getCurrentPanel } = require("dev/utils");
const { MessageChannel } = require("sdk/messaging");
const { when } = require("sdk/dom/events");
const { when } = require("sdk/dom/events-shimmed");
const { viewFor } = require("sdk/view/core");
const { createView } = require("dev/panel/view");

View File

@ -12,16 +12,20 @@ const {when} = require("sdk/dom/events");
var observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
const getActiveTab = (window=getMostRecentBrowserWindow()) =>
tabUtils.getActiveTab(window)
const openWindow = () => {
const window = open();
return new Promise((resolve) => {
observerService.addObserver({
addObserver({
observe(subject, topic) {
if (subject === window) {
observerService.removeObserver(this, topic);
removeObserver(this, topic);
resolve(subject);
}
}