--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -16,28 +16,18 @@ EXTRA_JS_MODULES.sdk += [
'source/app-extension/bootstrap.js',
]
EXTRA_JS_MODULES.sdk.system += [
'source/modules/system/Startup.js',
]
modules = [
- 'diffpatcher/diff.js',
- 'diffpatcher/index.js',
- 'diffpatcher/patch.js',
- 'diffpatcher/rebase.js',
- 'diffpatcher/test/common.js',
- 'diffpatcher/test/diff.js',
- 'diffpatcher/test/index.js',
- 'diffpatcher/test/patch.js',
- 'diffpatcher/test/tap.js',
'framescript/FrameScriptManager.jsm',
'framescript/content.jsm',
- 'framescript/context-menu.js',
'framescript/manager.js',
'framescript/util.js',
'index.js',
'jetpack-id/index.js',
'method/core.js',
'method/test/browser.js',
'method/test/common.js',
'mozilla-toolkit-versioning/index.js',
@@ -47,34 +37,28 @@ modules = [
'sdk/addon/window.js',
'sdk/base64.js',
'sdk/browser/events.js',
'sdk/clipboard.js',
'sdk/console/plain-text.js',
'sdk/console/traceback.js',
'sdk/content/content-worker.js',
'sdk/content/content.js',
- 'sdk/content/context-menu.js',
'sdk/content/events.js',
'sdk/content/l10n-html.js',
'sdk/content/loader.js',
'sdk/content/mod.js',
'sdk/content/page-mod.js',
'sdk/content/sandbox.js',
'sdk/content/sandbox/events.js',
'sdk/content/tab-events.js',
'sdk/content/thumbnail.js',
'sdk/content/utils.js',
'sdk/content/worker-child.js',
'sdk/content/worker.js',
- 'sdk/context-menu.js',
- 'sdk/context-menu/context.js',
- 'sdk/context-menu/core.js',
- 'sdk/context-menu/readers.js',
- 'sdk/context-menu@2.js',
'sdk/core/disposable.js',
'sdk/core/heritage.js',
'sdk/core/namespace.js',
'sdk/core/observer.js',
'sdk/core/promise.js',
'sdk/core/reference.js',
'sdk/deprecated/api-utils.js',
'sdk/deprecated/events/assembler.js',
@@ -89,20 +73,16 @@ modules = [
'sdk/event/core.js',
'sdk/event/dom.js',
'sdk/event/target.js',
'sdk/event/utils.js',
'sdk/frame/utils.js',
'sdk/fs/path.js',
'sdk/hotkeys.js',
'sdk/indexed-db.js',
- 'sdk/input/browser.js',
- 'sdk/input/customizable-ui.js',
- 'sdk/input/frame.js',
- 'sdk/input/system.js',
'sdk/io/buffer.js',
'sdk/io/byte-streams.js',
'sdk/io/file.js',
'sdk/io/fs.js',
'sdk/io/stream.js',
'sdk/io/text-streams.js',
'sdk/keyboard/hotkeys.js',
'sdk/keyboard/observer.js',
@@ -126,19 +106,16 @@ modules = [
'sdk/messaging.js',
'sdk/model/core.js',
'sdk/net/url.js',
'sdk/net/xhr.js',
'sdk/notifications.js',
'sdk/output/system.js',
'sdk/page-mod.js',
'sdk/page-mod/match-pattern.js',
- 'sdk/panel.js',
- 'sdk/panel/events.js',
- 'sdk/panel/utils.js',
'sdk/passwords.js',
'sdk/passwords/utils.js',
'sdk/platform/xpcom.js',
'sdk/preferences/event-target.js',
'sdk/preferences/native-options.js',
'sdk/preferences/service.js',
'sdk/preferences/utils.js',
'sdk/private-browsing.js',
@@ -183,39 +160,16 @@ modules = [
'sdk/test/harness.js',
'sdk/test/httpd.js',
'sdk/test/loader.js',
'sdk/test/memory.js',
'sdk/test/options.js',
'sdk/test/runner.js',
'sdk/test/utils.js',
'sdk/timers.js',
- 'sdk/ui.js',
- 'sdk/ui/button/action.js',
- 'sdk/ui/button/contract.js',
- 'sdk/ui/button/toggle.js',
- 'sdk/ui/button/view.js',
- 'sdk/ui/button/view/events.js',
- 'sdk/ui/component.js',
- 'sdk/ui/frame.js',
- 'sdk/ui/frame/model.js',
- 'sdk/ui/frame/view.html',
- 'sdk/ui/frame/view.js',
- 'sdk/ui/id.js',
- 'sdk/ui/sidebar.js',
- 'sdk/ui/sidebar/actions.js',
- 'sdk/ui/sidebar/contract.js',
- 'sdk/ui/sidebar/namespace.js',
- 'sdk/ui/sidebar/utils.js',
- 'sdk/ui/sidebar/view.js',
- 'sdk/ui/state.js',
- 'sdk/ui/state/events.js',
- 'sdk/ui/toolbar.js',
- 'sdk/ui/toolbar/model.js',
- 'sdk/ui/toolbar/view.js',
'sdk/uri/resource.js',
'sdk/url.js',
'sdk/url/utils.js',
'sdk/util/array.js',
'sdk/util/collection.js',
'sdk/util/contract.js',
'sdk/util/deprecate.js',
'sdk/util/dispatcher.js',
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/diff.js
+++ /dev/null
@@ -1,45 +0,0 @@
-"use strict";
-
-var method = require("../method/core")
-
-// Method is designed to work with data structures representing application
-// state. Calling it with a state should return object representing `delta`
-// that has being applied to a previous state to get to a current state.
-//
-// Example
-//
-// diff(state) // => { "item-id-1": { title: "some title" } "item-id-2": null }
-var diff = method("diff@diffpatcher")
-
-// diff between `null` / `undefined` to any hash is a hash itself.
-diff.define(null, function(from, to) { return to })
-diff.define(undefined, function(from, to) { return to })
-diff.define(Object, function(from, to) {
- return calculate(from, to || {}) || {}
-})
-
-function calculate(from, to) {
- var diff = {}
- var changes = 0
- Object.keys(from).forEach(function(key) {
- changes = changes + 1
- if (!(key in to) && from[key] != null) diff[key] = null
- else changes = changes - 1
- })
- Object.keys(to).forEach(function(key) {
- changes = changes + 1
- var previous = from[key]
- var current = to[key]
- if (previous === current) return (changes = changes - 1)
- if (typeof(current) !== "object") return diff[key] = current
- if (typeof(previous) !== "object") return diff[key] = current
- var delta = calculate(previous, current)
- if (delta) diff[key] = delta
- else changes = changes - 1
- })
- return changes ? diff : null
-}
-
-diff.calculate = calculate
-
-module.exports = diff
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-"use strict";
-
-exports.diff = require("./diff")
-exports.patch = require("./patch")
-exports.rebase = require("./rebase")
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/patch.js
+++ /dev/null
@@ -1,21 +0,0 @@
-"use strict";
-
-var method = require("../method/core")
-var rebase = require("./rebase")
-
-// Method is designed to work with data structures representing application
-// state. Calling it with a state and delta should return object representing
-// new state, with changes in `delta` being applied to previous.
-//
-// ## Example
-//
-// patch(state, {
-// "item-id-1": { completed: false }, // update
-// "item-id-2": null // delete
-// })
-var patch = method("patch@diffpatcher")
-patch.define(Object, function patch(hash, delta) {
- return rebase({}, hash, delta)
-})
-
-module.exports = patch
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/rebase.js
+++ /dev/null
@@ -1,36 +0,0 @@
-"use strict";
-
-var nil = {}
-var owns = ({}).hasOwnProperty
-
-function rebase(result, parent, delta) {
- var key, current, previous, update
- for (key in parent) {
- if (owns.call(parent, key)) {
- previous = parent[key]
- update = owns.call(delta, key) ? delta[key] : nil
- if (previous === null) continue
- else if (previous === void(0)) continue
- else if (update === null) continue
- else if (update === void(0)) continue
- else result[key] = previous
- }
- }
- for (key in delta) {
- if (owns.call(delta, key)) {
- update = delta[key]
- current = owns.call(result, key) ? result[key] : nil
- if (current === update) continue
- else if (update === null) continue
- else if (update === void(0)) continue
- else if (current === nil) result[key] = update
- else if (typeof(update) !== "object") result[key] = update
- else if (typeof(current) !== "object") result[key] = update
- else result[key]= rebase({}, current, update)
- }
- }
-
- return result
-}
-
-module.exports = rebase
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/test/common.js
+++ /dev/null
@@ -1,3 +0,0 @@
-"use strict";
-
-require("test").run(require("./index"))
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/test/diff.js
+++ /dev/null
@@ -1,59 +0,0 @@
-"use strict";
-
-var diff = require("../diff")
-
-exports["test diff from null"] = function(assert) {
- var to = { a: 1, b: 2 }
- assert.equal(diff(null, to), to, "diff null to x returns x")
- assert.equal(diff(void(0), to), to, "diff undefined to x returns x")
-
-}
-
-exports["test diff to null"] = function(assert) {
- var from = { a: 1, b: 2 }
- assert.deepEqual(diff({ a: 1, b: 2 }, null),
- { a: null, b: null },
- "diff x null returns x with all properties nullified")
-}
-
-exports["test diff identical"] = function(assert) {
- assert.deepEqual(diff({}, {}), {}, "diff on empty objects is {}")
-
- assert.deepEqual(diff({ a: 1, b: 2 }, { a: 1, b: 2 }), {},
- "if properties match diff is {}")
-
- assert.deepEqual(diff({ a: 1, b: { c: { d: 3, e: 4 } } },
- { a: 1, b: { c: { d: 3, e: 4 } } }), {},
- "diff between identical nested hashes is {}")
-
-}
-
-exports["test diff delete"] = function(assert) {
- assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2 }), { a: null },
- "missing property is deleted")
- assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2 }), { a: 2, b: null },
- "missing property is deleted another updated")
- assert.deepEqual(diff({ a: 1, b: 2 }, {}), { a: null, b: null },
- "missing propertes are deleted")
- assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, {}),
- { a: null, b: null },
- "missing deep propertes are deleted")
- assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { b: { c: {} } }),
- { a: null, b: { c: { d: null } } },
- "missing nested propertes are deleted")
-}
-
-exports["test add update"] = function(assert) {
- assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2, c: 3 }), { a: null, c: 3 },
- "delete and add")
- assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2, c: 3 }), { a: 2, b: null, c: 3 },
- "delete and adds")
- assert.deepEqual(diff({}, { a: 1, b: 2 }), { a: 1, b: 2 },
- "diff on empty objcet returns equivalen of to")
- assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { d: 3 }),
- { a: null, b: null, d: 3 },
- "missing deep propertes are deleted")
- assert.deepEqual(diff({ b: { c: {} }, d: null }, { a: 1, b: { c: { d: 2 } } }),
- { a: 1, b: { c: { d: 2 } } },
- "missing nested propertes are deleted")
-}
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/test/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-"use strict";
-
-var diff = require("../diff")
-var patch = require("../patch")
-
-exports["test diff"] = require("./diff")
-exports["test patch"] = require("./patch")
-
-exports["test patch(a, diff(a, b)) => b"] = function(assert) {
- var a = { a: { b: 1 }, c: { d: 2 } }
- var b = { a: { e: 3 }, c: { d: 4 } }
-
- assert.deepEqual(patch(a, diff(a, b)), b, "patch(a, diff(a, b)) => b")
-}
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/test/patch.js
+++ /dev/null
@@ -1,83 +0,0 @@
-"use strict";
-
-var patch = require("../patch")
-
-exports["test patch delete"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, { a: null }), { b: 2 }, "null removes property")
-}
-
-exports["test patch delete with void"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
- "void(0) removes property")
-}
-
-exports["test patch delete missing"] = function(assert) {
- assert.deepEqual(patch({ a: 1, b: 2 }, { c: null }),
- { a: 1, b: 2 },
- "null removes property if exists");
-
- assert.deepEqual(patch({ a: 1, b: 2 }, { c: void(0) }),
- { a: 1, b: 2 },
- "void removes property if exists");
-}
-
-exports["test delete deleted"] = function(assert) {
- assert.deepEqual(patch({ a: null, b: 2, c: 3, d: void(0)},
- { a: void(0), b: null, d: null }),
- {c: 3},
- "removed all existing and non existing");
-}
-
-exports["test update deleted"] = function(assert) {
- assert.deepEqual(patch({ a: null, b: void(0), c: 3},
- { a: { b: 2 } }),
- { a: { b: 2 }, c: 3 },
- "replace deleted");
-}
-
-exports["test patch delete with void"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
- "void(0) removes property")
-}
-
-
-exports["test patch addition"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
- "new properties are added")
-}
-
-exports["test patch addition"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
- "new properties are added")
-}
-
-exports["test hash on itself"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, hash), hash,
- "applying hash to itself returns hash itself")
-}
-
-exports["test patch with empty delta"] = function(assert) {
- var hash = { a: 1, b: 2 }
-
- assert.deepEqual(patch(hash, {}), hash,
- "applying empty delta results in no changes")
-}
-
-exports["test patch nested data"] = function(assert) {
- assert.deepEqual(patch({ a: { b: 1 }, c: { d: 2 } },
- { a: { b: null, e: 3 }, c: { d: 4 } }),
- { a: { e: 3 }, c: { d: 4 } },
- "nested structures can also be patched")
-}
deleted file mode 100644
--- a/addon-sdk/source/lib/diffpatcher/test/tap.js
+++ /dev/null
@@ -1,3 +0,0 @@
-"use strict";
-
-require("retape")(require("./index"))
deleted file mode 100644
--- a/addon-sdk/source/lib/framescript/context-menu.js
+++ /dev/null
@@ -1,215 +0,0 @@
-/* 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";
-
-const { query, constant, cache } = require("sdk/lang/functional");
-const { pairs, each, map, object } = require("sdk/util/sequence");
-const { nodeToMessageManager } = require("./util");
-
-// Decorator function that takes `f` function and returns one that attempts
-// to run `f` with given arguments. In case of exception error is logged
-// and `fallback` is returned instead.
-const Try = (fn, fallback=null) => (...args) => {
- try {
- return fn(...args);
- } catch(error) {
- console.error(error);
- return fallback;
- }
-};
-
-// Decorator funciton that takes `f` function and returns one that returns
-// JSON cloned result of whatever `f` returns for given arguments.
-const JSONReturn = f => (...args) => JSON.parse(JSON.stringify(f(...args)));
-
-const Null = constant(null);
-
-// Table of readers mapped to field names they're going to be reading.
-const readers = Object.create(null);
-// Read function takes "contextmenu" event target `node` and returns table of
-// read field names mapped to appropriate values. Read uses above defined read
-// table to read data for all registered readers.
-const read = node =>
- object(...map(([id, read]) => [id, read(node, id)], pairs(readers)));
-
-// Table of built-in readers, each takes a descriptor and returns a reader:
-// descriptor -> node -> JSON
-const parsers = Object.create(null)
-// Function takes a descriptor of the remotely defined reader and parsese it
-// to construct a local reader that's going to read out data from context menu
-// target.
-const parse = descriptor => {
- const parser = parsers[descriptor.category];
- if (!parser) {
- console.error("Unknown reader descriptor was received", descriptor, `"${descriptor.category}"`);
- return Null
- }
- return Try(parser(descriptor));
-}
-
-// TODO: Test how chrome's mediaType behaves to try and match it's behavior.
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const SVG_NS = "http://www.w3.org/2000/svg";
-
-// Firefox always creates a HTMLVideoElement when loading an ogg file
-// directly. If the media is actually audio, be smarter and provide a
-// context menu with audio operations.
-// Source: https://github.com/mozilla/gecko-dev/blob/28c2fca3753c5371643843fc2f2f205146b083b7/browser/base/content/nsContextMenu.js#L632-L637
-const isVideoLoadingAudio = node =>
- node.readyState >= node.HAVE_METADATA &&
- (node.videoWidth == 0 || node.videoHeight == 0)
-
-const isVideo = node =>
- node instanceof node.ownerGlobal.HTMLVideoElement &&
- !isVideoLoadingAudio(node);
-
-const isAudio = node => {
- const {HTMLVideoElement, HTMLAudioElement} = node.ownerGlobal;
- return node instanceof HTMLAudioElement ? true :
- node instanceof HTMLVideoElement ? isVideoLoadingAudio(node) :
- false;
-};
-
-const isImage = ({namespaceURI, localName}) =>
- namespaceURI === HTML_NS && localName === "img" ? true :
- namespaceURI === XUL_NS && localName === "image" ? true :
- namespaceURI === SVG_NS && localName === "image" ? true :
- false;
-
-parsers["reader/MediaType()"] = constant(node =>
- isImage(node) ? "image" :
- isAudio(node) ? "audio" :
- isVideo(node) ? "video" :
- null);
-
-
-const readLink = node =>
- node.namespaceURI === HTML_NS && node.localName === "a" ? node.href :
- readLink(node.parentNode);
-
-parsers["reader/LinkURL()"] = constant(node =>
- node.matches("a, a *") ? readLink(node) : null);
-
-// Reader that reads out `true` if "contextmenu" `event.target` matches
-// `descriptor.selector` and `false` if it does not.
-parsers["reader/SelectorMatch()"] = ({selector}) =>
- node => node.matches(selector);
-
-// Accessing `selectionStart` and `selectionEnd` properties on non
-// editable input nodes throw exceptions, there for we need this util
-// function to guard us against them.
-const getInputSelection = node => {
- try {
- if ("selectionStart" in node && "selectionEnd" in node) {
- const {selectionStart, selectionEnd} = node;
- return {selectionStart, selectionEnd}
- }
- }
- catch(_) {}
-
- return null;
-}
-
-// Selection reader does not really cares about descriptor so it is
-// a constant function returning selection reader. Selection reader
-// returns string of the selected text or `null` if there is no selection.
-parsers["reader/Selection()"] = constant(node => {
- const selection = node.ownerDocument.getSelection();
- if (!selection.isCollapsed) {
- return selection.toString();
- }
- // If target node is editable (text, input, textarea, etc..) document does
- // not really handles selections there. There for we fallback to checking
- // `selectionStart` `selectionEnd` properties and if they are present we
- // extract selections manually from the `node.value`.
- else {
- const selection = getInputSelection(node);
- const isSelected = selection &&
- Number.isInteger(selection.selectionStart) &&
- Number.isInteger(selection.selectionEnd) &&
- selection.selectionStart !== selection.selectionEnd;
- return isSelected ? node.value.substring(selection.selectionStart,
- selection.selectionEnd) :
- null;
- }
-});
-
-// Query reader just reads out properties from the node, so we just use `query`
-// utility function.
-parsers["reader/Query()"] = ({path}) => JSONReturn(query(path));
-// Attribute reader just reads attribute of the event target node.
-parsers["reader/Attribute()"] = ({name}) => node => node.getAttribute(name);
-
-// Extractor reader defines generates a reader out of serialized function, who's
-// return value is JSON cloned. Note: We do know source will evaluate to function
-// as that's what we serialized on the other end, it's also ok if generated function
-// is going to throw as registered readers are wrapped in try catch to avoid breakting
-// unrelated readers.
-parsers["reader/Extractor()"] = ({source}) =>
- JSONReturn(new Function("return (" + source + ")")());
-
-// If the context-menu target node or any of its ancestors is one of these,
-// Firefox uses a tailored context menu, and so the page context doesn't apply.
-// There for `reader/isPage()` will read `false` in that case otherwise it's going
-// to read `true`.
-const nonPageElements = ["a", "applet", "area", "button", "canvas", "object",
- "embed", "img", "input", "map", "video", "audio", "menu",
- "option", "select", "textarea", "[contenteditable=true]"];
-const nonPageSelector = nonPageElements.
- concat(nonPageElements.map(tag => `${tag} *`)).
- join(", ");
-
-// Note: isPageContext implementation could have actually used SelectorMatch reader,
-// but old implementation was also checked for collapsed selection there for to keep
-// the behavior same we end up implementing a new reader.
-parsers["reader/isPage()"] = constant(node =>
- node.ownerGlobal.getSelection().isCollapsed &&
- !node.matches(nonPageSelector));
-
-// Reads `true` if node is in an iframe otherwise returns true.
-parsers["reader/isFrame()"] = constant(node =>
- !!node.ownerGlobal.frameElement);
-
-parsers["reader/isEditable()"] = constant(node => {
- const selection = getInputSelection(node);
- return selection ? !node.readOnly && !node.disabled : node.isContentEditable;
-});
-
-
-// TODO: Add some reader to read out tab id.
-
-const onReadersUpdate = message => {
- each(([id, descriptor]) => {
- if (descriptor) {
- readers[id] = parse(descriptor);
- }
- else {
- delete readers[id];
- }
- }, pairs(message.data));
-};
-exports.onReadersUpdate = onReadersUpdate;
-
-
-const onContextMenu = event => {
- if (!event.defaultPrevented) {
- const manager = nodeToMessageManager(event.target);
- manager.sendSyncMessage("sdk/context-menu/read", read(event.target), readers);
- }
-};
-exports.onContextMenu = onContextMenu;
-
-
-const onContentFrame = (frame) => {
- // Listen for contextmenu events in on this frame.
- frame.addEventListener("contextmenu", onContextMenu);
- // Listen to registered reader changes and update registry.
- frame.addMessageListener("sdk/context-menu/readers", onReadersUpdate);
-
- // Request table of readers (if this is loaded in a new process some table
- // changes may be missed, this is way to sync up).
- frame.sendAsyncMessage("sdk/context-menu/readers?");
-};
-exports.onContentFrame = onContentFrame;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/content/context-menu.js
+++ /dev/null
@@ -1,407 +0,0 @@
-/* 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";
-
-const { Class } = require("../core/heritage");
-const self = require("../self");
-const { WorkerChild } = require("./worker-child");
-const { getInnerId } = require("../window/utils");
-const { Ci } = require("chrome");
-const { Services } = require("resource://gre/modules/Services.jsm");
-const system = require('../system/events');
-const { process } = require('../remote/child');
-
-// These functions are roughly copied from sdk/selection which doesn't work
-// in the content process
-function getElementWithSelection(window) {
- let element = Services.focus.getFocusedElementForWindow(window, false, {});
- if (!element)
- return null;
-
- try {
- // Accessing selectionStart and selectionEnd on e.g. a button
- // results in an exception thrown as per the HTML5 spec. See
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
-
- let { value, selectionStart, selectionEnd } = element;
-
- let hasSelection = typeof value === "string" &&
- !isNaN(selectionStart) &&
- !isNaN(selectionEnd) &&
- selectionStart !== selectionEnd;
-
- return hasSelection ? element : null;
- }
- catch (err) {
- console.exception(err);
- return null;
- }
-}
-
-function safeGetRange(selection, rangeNumber) {
- try {
- let { rangeCount } = selection;
- let range = null;
-
- for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) {
- range = selection.getRangeAt(rangeNumber);
-
- if (range && range.toString())
- break;
-
- range = null;
- }
-
- return range;
- }
- catch (e) {
- return null;
- }
-}
-
-function getSelection(window) {
- let selection = window.getSelection();
- let range = safeGetRange(selection);
- if (range)
- return range.toString();
-
- let node = getElementWithSelection(window);
- if (!node)
- return null;
-
- return node.value.substring(node.selectionStart, node.selectionEnd);
-}
-
-//These are used by PageContext.isCurrent below. If the popupNode or any of
-//its ancestors is one of these, Firefox uses a tailored context menu, and so
-//the page context doesn't apply.
-const NON_PAGE_CONTEXT_ELTS = [
- Ci.nsIDOMHTMLAnchorElement,
- Ci.nsIDOMHTMLAreaElement,
- Ci.nsIDOMHTMLButtonElement,
- Ci.nsIDOMHTMLCanvasElement,
- Ci.nsIDOMHTMLEmbedElement,
- Ci.nsIDOMHTMLImageElement,
- Ci.nsIDOMHTMLInputElement,
- Ci.nsIDOMHTMLMapElement,
- Ci.nsIDOMHTMLMediaElement,
- Ci.nsIDOMHTMLMenuElement,
- Ci.nsIDOMHTMLObjectElement,
- Ci.nsIDOMHTMLOptionElement,
- Ci.nsIDOMHTMLSelectElement,
- Ci.nsIDOMHTMLTextAreaElement,
-];
-
-// List all editable types of inputs. Or is it better to have a list
-// of non-editable inputs?
-var editableInputs = {
- email: true,
- number: true,
- password: true,
- search: true,
- tel: true,
- text: true,
- textarea: true,
- url: true
-};
-
-var CONTEXTS = {};
-
-var Context = Class({
- initialize: function(id) {
- this.id = id;
- },
-
- adjustPopupNode: function adjustPopupNode(popupNode) {
- return popupNode;
- },
-
- // Gets state to pass through to the parent process for the node the user
- // clicked on
- getState: function(popupNode) {
- return false;
- }
-});
-
-// Matches when the context-clicked node doesn't have any of
-// NON_PAGE_CONTEXT_ELTS in its ancestors
-CONTEXTS.PageContext = Class({
- extends: Context,
-
- getState: function(popupNode) {
- // If there is a selection in the window then this context does not match
- if (!popupNode.ownerGlobal.getSelection().isCollapsed)
- return false;
-
- // If the clicked node or any of its ancestors is one of the blocked
- // NON_PAGE_CONTEXT_ELTS then this context does not match
- while (!(popupNode instanceof Ci.nsIDOMDocument)) {
- if (NON_PAGE_CONTEXT_ELTS.some(type => popupNode instanceof type))
- return false;
-
- popupNode = popupNode.parentNode;
- }
-
- return true;
- }
-});
-
-// Matches when there is an active selection in the window
-CONTEXTS.SelectionContext = Class({
- extends: Context,
-
- getState: function(popupNode) {
- if (!popupNode.ownerGlobal.getSelection().isCollapsed)
- return true;
-
- try {
- // The node may be a text box which has selectionStart and selectionEnd
- // properties. If not this will throw.
- let { selectionStart, selectionEnd } = popupNode;
- return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
- selectionStart !== selectionEnd;
- }
- catch (e) {
- return false;
- }
- }
-});
-
-// Matches when the context-clicked node or any of its ancestors matches the
-// selector given
-CONTEXTS.SelectorContext = Class({
- extends: Context,
-
- initialize: function initialize(id, selector) {
- Context.prototype.initialize.call(this, id);
- this.selector = selector;
- },
-
- adjustPopupNode: function adjustPopupNode(popupNode) {
- let selector = this.selector;
-
- while (!(popupNode instanceof Ci.nsIDOMDocument)) {
- if (popupNode.matches(selector))
- return popupNode;
-
- popupNode = popupNode.parentNode;
- }
-
- return null;
- },
-
- getState: function(popupNode) {
- return !!this.adjustPopupNode(popupNode);
- }
-});
-
-// Matches when the page url matches any of the patterns given
-CONTEXTS.URLContext = Class({
- extends: Context,
-
- getState: function(popupNode) {
- return popupNode.ownerDocument.URL;
- }
-});
-
-// Matches when the user-supplied predicate returns true
-CONTEXTS.PredicateContext = Class({
- extends: Context,
-
- getState: function(node) {
- let window = node.ownerGlobal;
- let data = {};
-
- data.documentType = node.ownerDocument.contentType;
-
- data.documentURL = node.ownerDocument.location.href;
- data.targetName = node.nodeName.toLowerCase();
- data.targetID = node.id || null ;
-
- if ((data.targetName === 'input' && editableInputs[node.type]) ||
- data.targetName === 'textarea') {
- data.isEditable = !node.readOnly && !node.disabled;
- }
- else {
- data.isEditable = node.isContentEditable;
- }
-
- data.selectionText = getSelection(window, "TEXT");
-
- data.srcURL = node.src || null;
- data.value = node.value || null;
-
- while (!data.linkURL && node) {
- data.linkURL = node.href || null;
- node = node.parentNode;
- }
-
- return data;
- },
-});
-
-function instantiateContext({ id, type, args }) {
- if (!(type in CONTEXTS)) {
- console.error("Attempt to use unknown context " + type);
- return;
- }
- return new CONTEXTS[type](id, ...args);
-}
-
-var ContextWorker = Class({
- implements: [ WorkerChild ],
-
- // Calls the context workers context listeners and returns the first result
- // that is either a string or a value that evaluates to true. If all of the
- // listeners returned false then returns false. If there are no listeners,
- // returns true (show the menu item by default).
- getMatchedContext: function getCurrentContexts(popupNode) {
- let results = this.sandbox.emitSync("context", popupNode);
- if (!results.length)
- return true;
- return results.reduce((val, result) => val || result);
- },
-
- // Emits a click event in the worker's port. popupNode is the node that was
- // context-clicked, and clickedItemData is the data of the item that was
- // clicked.
- fireClick: function fireClick(popupNode, clickedItemData) {
- this.sandbox.emitSync("click", popupNode, clickedItemData);
- }
-});
-
-// Gets the item's content script worker for a window, creating one if necessary
-// Once created it will be automatically destroyed when the window unloads.
-// If there is not content scripts for the item then null will be returned.
-function getItemWorkerForWindow(item, window) {
- if (!item.contentScript && !item.contentScriptFile)
- return null;
-
- let id = getInnerId(window);
- let worker = item.workerMap.get(id);
-
- if (worker)
- return worker;
-
- worker = ContextWorker({
- id: item.id,
- window,
- manager: item.manager,
- contentScript: item.contentScript,
- contentScriptFile: item.contentScriptFile,
- onDetach: function() {
- item.workerMap.delete(id);
- }
- });
-
- item.workerMap.set(id, worker);
-
- return worker;
-}
-
-// A very simple remote proxy for every item. It's job is to provide data for
-// the main process to use to determine visibility state and to call into
-// content scripts when clicked.
-var RemoteItem = Class({
- initialize: function(options, manager) {
- this.id = options.id;
- this.contexts = options.contexts.map(instantiateContext);
- this.contentScript = options.contentScript;
- this.contentScriptFile = options.contentScriptFile;
-
- this.manager = manager;
-
- this.workerMap = new Map();
- keepAlive.set(this.id, this);
- },
-
- destroy: function() {
- for (let worker of this.workerMap.values()) {
- worker.destroy();
- }
- keepAlive.delete(this.id);
- },
-
- activate: function(popupNode, data) {
- let worker = getItemWorkerForWindow(this, popupNode.ownerGlobal);
- if (!worker)
- return;
-
- for (let context of this.contexts)
- popupNode = context.adjustPopupNode(popupNode);
-
- worker.fireClick(popupNode, data);
- },
-
- // Fills addonInfo with state data to send through to the main process
- getContextState: function(popupNode, addonInfo) {
- if (!(self.id in addonInfo)) {
- addonInfo[self.id] = {
- processID: process.id,
- items: {}
- };
- }
-
- let worker = getItemWorkerForWindow(this, popupNode.ownerGlobal);
- let contextStates = {};
- for (let context of this.contexts)
- contextStates[context.id] = context.getState(popupNode);
-
- addonInfo[self.id].items[this.id] = {
- // It isn't ideal to create a PageContext for every item but there isn't
- // a good shared place to do it.
- pageContext: (new CONTEXTS.PageContext()).getState(popupNode),
- contextStates,
- hasWorker: !!worker,
- workerContext: worker ? worker.getMatchedContext(popupNode) : true
- }
- }
-});
-exports.RemoteItem = RemoteItem;
-
-// Holds remote items for this frame.
-var keepAlive = new Map();
-
-// Called to create remote proxies for items. If they already exist we destroy
-// and recreate. This can happen if the item changes in some way or in odd
-// timing cases where the frame script is create around the same time as the
-// item is created in the main process
-process.port.on('sdk/contextmenu/createitems', (process, items) => {
- for (let itemoptions of items) {
- let oldItem = keepAlive.get(itemoptions.id);
- if (oldItem) {
- oldItem.destroy();
- }
-
- let item = new RemoteItem(itemoptions, this);
- }
-});
-
-process.port.on('sdk/contextmenu/destroyitems', (process, items) => {
- for (let id of items) {
- let item = keepAlive.get(id);
- item.destroy();
- }
-});
-
-var lastPopupNode = null;
-
-system.on('content-contextmenu', ({ subject }) => {
- let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject;
- lastPopupNode = popupNode;
-
- for (let item of keepAlive.values()) {
- item.getContextState(popupNode, addonInfo);
- }
-}, true);
-
-process.port.on('sdk/contextmenu/activateitems', (process, items, data) => {
- for (let id of items) {
- let item = keepAlive.get(id);
- if (!item)
- continue;
-
- item.activate(lastPopupNode, data);
- }
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/context-menu.js
+++ /dev/null
@@ -1,1187 +0,0 @@
-/* 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": "stable",
- "engines": {
- // TODO Fennec support Bug 788334
- "Firefox": "*",
- "SeaMonkey": "*"
- }
-};
-
-const { Class, mix } = require("./core/heritage");
-const { ns } = require("./core/namespace");
-lazyRequire(this, "./deprecated/api-utils", "validateOptions", "getTypeOf");
-lazyRequire(this, "./url", "URL", "isValidURI");
-lazyRequire(this, "./deprecated/window-utils", "WindowTracker", "browserWindowIterator");
-lazyRequire(this, "./window/utils", "isBrowser", "getInnerId");
-lazyRequire(this, "./util/match-pattern", "MatchPattern");
-const { EventTarget } = require("./event/target");
-lazyRequire(this, './event/core', "emit");
-const { when } = require('./system/unload');
-const { contract: loaderContract } = require('./content/loader');
-const { omit } = require('./util/object');
-lazyRequireModule(this, './self', "self");
-const { remoteRequire, processes } = require('./remote/parent');
-remoteRequire('sdk/content/context-menu');
-
-// All user items we add have this class.
-const ITEM_CLASS = "addon-context-menu-item";
-
-// Items in the top-level context menu also have this class.
-const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel";
-
-// Items in the overflow submenu also have this class.
-const OVERFLOW_ITEM_CLASS = "addon-context-menu-item-overflow";
-
-// The class of the menu separator that separates standard context menu items
-// from our user items.
-const SEPARATOR_CLASS = "addon-context-menu-separator";
-
-// If more than this number of items are added to the context menu, all items
-// overflow into a "Jetpack" submenu.
-const OVERFLOW_THRESH_DEFAULT = 10;
-const OVERFLOW_THRESH_PREF =
- "extensions.addon-sdk.context-menu.overflowThreshold";
-
-// The label of the overflow sub-xul:menu.
-//
-// TODO: Localize these.
-const OVERFLOW_MENU_LABEL = "Add-ons";
-const OVERFLOW_MENU_ACCESSKEY = "A";
-
-// The class of the overflow sub-xul:menu.
-const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
-
-// The class of the overflow submenu's xul:menupopup.
-const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";
-
-// Holds private properties for API objects
-var internal = ns();
-
-// A little hacky but this is the last process ID that last opened the context
-// menu
-var lastContextProcessId = null;
-
-var uuidModule = require('./util/uuid');
-function uuid() {
- return uuidModule.uuid().toString();
-}
-
-function getScheme(spec) {
- try {
- return URL(spec).scheme;
- }
- catch(e) {
- return null;
- }
-}
-
-var Context = Class({
- initialize: function() {
- internal(this).id = uuid();
- },
-
- // Returns the node that made this context current
- adjustPopupNode: function adjustPopupNode(popupNode) {
- return popupNode;
- },
-
- // Returns whether this context is current for the current node
- isCurrent: function isCurrent(state) {
- return state;
- }
-});
-
-// Matches when the context-clicked node doesn't have any of
-// NON_PAGE_CONTEXT_ELTS in its ancestors
-var PageContext = Class({
- extends: Context,
-
- serialize: function() {
- return {
- id: internal(this).id,
- type: "PageContext",
- args: []
- }
- }
-});
-exports.PageContext = PageContext;
-
-// Matches when there is an active selection in the window
-var SelectionContext = Class({
- extends: Context,
-
- serialize: function() {
- return {
- id: internal(this).id,
- type: "SelectionContext",
- args: []
- }
- }
-});
-exports.SelectionContext = SelectionContext;
-
-// Matches when the context-clicked node or any of its ancestors matches the
-// selector given
-var SelectorContext = Class({
- extends: Context,
-
- initialize: function initialize(selector) {
- Context.prototype.initialize.call(this);
- let options = validateOptions({ selector: selector }, {
- selector: {
- is: ["string"],
- msg: "selector must be a string."
- }
- });
- internal(this).selector = options.selector;
- },
-
- serialize: function() {
- return {
- id: internal(this).id,
- type: "SelectorContext",
- args: [internal(this).selector]
- }
- }
-});
-exports.SelectorContext = SelectorContext;
-
-// Matches when the page url matches any of the patterns given
-var URLContext = Class({
- extends: Context,
-
- initialize: function initialize(patterns) {
- Context.prototype.initialize.call(this);
- patterns = Array.isArray(patterns) ? patterns : [patterns];
-
- try {
- internal(this).patterns = patterns.map(p => new MatchPattern(p));
- }
- catch (err) {
- throw new Error("Patterns must be a string, regexp or an array of " +
- "strings or regexps: " + err);
- }
- },
-
- isCurrent: function isCurrent(url) {
- return internal(this).patterns.some(p => p.test(url));
- },
-
- serialize: function() {
- return {
- id: internal(this).id,
- type: "URLContext",
- args: []
- }
- }
-});
-exports.URLContext = URLContext;
-
-// Matches when the user-supplied predicate returns true
-var PredicateContext = Class({
- extends: Context,
-
- initialize: function initialize(predicate) {
- Context.prototype.initialize.call(this);
- let options = validateOptions({ predicate: predicate }, {
- predicate: {
- is: ["function"],
- msg: "predicate must be a function."
- }
- });
- internal(this).predicate = options.predicate;
- },
-
- isCurrent: function isCurrent(state) {
- return internal(this).predicate(state);
- },
-
- serialize: function() {
- return {
- id: internal(this).id,
- type: "PredicateContext",
- args: []
- }
- }
-});
-exports.PredicateContext = PredicateContext;
-
-function removeItemFromArray(array, item) {
- return array.filter(i => i !== item);
-}
-
-// Converts anything that isn't false, null or undefined into a string
-function stringOrNull(val) {
- return val ? String(val) : val;
-}
-
-// Shared option validation rules for Item, Menu, and Separator
-var baseItemRules = {
- parentMenu: {
- is: ["object", "undefined"],
- ok: function (v) {
- if (!v)
- return true;
- return (v instanceof ItemContainer) || (v instanceof Menu);
- },
- msg: "parentMenu must be a Menu or not specified."
- },
- context: {
- is: ["undefined", "object", "array"],
- ok: function (v) {
- if (!v)
- return true;
- let arr = Array.isArray(v) ? v : [v];
- return arr.every(o => o instanceof Context);
- },
- msg: "The 'context' option must be a Context object or an array of " +
- "Context objects."
- },
- onMessage: {
- is: ["function", "undefined"]
- },
- contentScript: loaderContract.rules.contentScript,
- contentScriptFile: loaderContract.rules.contentScriptFile
-};
-
-var labelledItemRules = mix(baseItemRules, {
- label: {
- map: stringOrNull,
- is: ["string"],
- ok: v => !!v,
- msg: "The item must have a non-empty string label."
- },
- accesskey: {
- map: stringOrNull,
- is: ["string", "undefined", "null"],
- ok: (v) => {
- if (!v) {
- return true;
- }
- return typeof v == "string" && v.length === 1;
- },
- msg: "The item must have a single character accesskey, or no accesskey."
- },
- image: {
- map: stringOrNull,
- is: ["string", "undefined", "null"],
- ok: function (url) {
- if (!url)
- return true;
- return isValidURI(url);
- },
- msg: "Image URL validation failed"
- }
-});
-
-// Additional validation rules for Item
-var itemRules = mix(labelledItemRules, {
- data: {
- map: stringOrNull,
- is: ["string", "undefined", "null"]
- }
-});
-
-// Additional validation rules for Menu
-var menuRules = mix(labelledItemRules, {
- items: {
- is: ["array", "undefined"],
- ok: function (v) {
- if (!v)
- return true;
- return v.every(function (item) {
- return item instanceof BaseItem;
- });
- },
- msg: "items must be an array, and each element in the array must be an " +
- "Item, Menu, or Separator."
- }
-});
-
-// Returns true if any contexts match. If there are no contexts then a
-// PageContext is tested instead
-function hasMatchingContext(contexts, addonInfo) {
- for (let context of contexts) {
- if (!(internal(context).id in addonInfo.contextStates)) {
- console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules.");
- return false;
- }
- if (!context.isCurrent(addonInfo.contextStates[internal(context).id]))
- return false;
- }
-
- return true;
-}
-
-// Tests whether an item should be visible or not based on its contexts and
-// content scripts
-function isItemVisible(item, addonInfo, usePageWorker) {
- if (!item.context.length) {
- if (!addonInfo.hasWorker)
- return usePageWorker ? addonInfo.pageContext : true;
- }
-
- if (!hasMatchingContext(item.context, addonInfo))
- return false;
-
- let context = addonInfo.workerContext;
- if (typeof(context) === "string" && context != "")
- item.label = context;
-
- return !!context;
-}
-
-// Called when an item is clicked to send out click events to the content
-// scripts
-function itemActivated(item, clickedNode) {
- let items = [internal(item).id];
- let data = item.data;
-
- while (item.parentMenu) {
- item = item.parentMenu;
- items.push(internal(item).id);
- }
-
- let process = processes.getById(lastContextProcessId);
- if (process)
- process.port.emit('sdk/contextmenu/activateitems', items, data);
-}
-
-function serializeItem(item) {
- return {
- id: internal(item).id,
- contexts: item.context.map(c => c.serialize()),
- contentScript: item.contentScript,
- contentScriptFile: item.contentScriptFile,
- };
-}
-
-// All things that appear in the context menu extend this
-var BaseItem = Class({
- initialize: function initialize() {
- internal(this).id = uuid();
-
- internal(this).contexts = [];
- if ("context" in internal(this).options && internal(this).options.context) {
- let contexts = internal(this).options.context;
- if (Array.isArray(contexts)) {
- for (let context of contexts)
- internal(this).contexts.push(context);
- }
- else {
- internal(this).contexts.push(contexts);
- }
- }
-
- let parentMenu = internal(this).options.parentMenu;
- if (!parentMenu)
- parentMenu = contentContextMenu;
-
- parentMenu.addItem(this);
-
- Object.defineProperty(this, "contentScript", {
- enumerable: true,
- value: internal(this).options.contentScript
- });
-
- // Resolve URIs here as tests may have overriden self
- let files = internal(this).options.contentScriptFile;
- if (files) {
- if (!Array.isArray(files))
- files = [files];
- files = files.map(self.data.url);
- }
- internal(this).options.contentScriptFile = files;
- Object.defineProperty(this, "contentScriptFile", {
- enumerable: true,
- value: internal(this).options.contentScriptFile
- });
-
- // Notify all frames of this new item
- sendItems([serializeItem(this)]);
- },
-
- destroy: function destroy() {
- if (internal(this).destroyed)
- return;
-
- // Tell all existing frames that this item has been destroyed
- processes.port.emit("sdk/contextmenu/destroyitems", [internal(this).id]);
-
- if (this.parentMenu)
- this.parentMenu.removeItem(this);
-
- internal(this).destroyed = true;
- },
-
- get context() {
- let contexts = internal(this).contexts.slice(0);
- contexts.add = (context) => {
- internal(this).contexts.push(context);
- // Notify all frames that this item has changed
- sendItems([serializeItem(this)]);
- };
- contexts.remove = (context) => {
- internal(this).contexts = internal(this).contexts.filter(c => {
- return c != context;
- });
- // Notify all frames that this item has changed
- sendItems([serializeItem(this)]);
- };
- return contexts;
- },
-
- set context(val) {
- internal(this).contexts = val.slice(0);
- // Notify all frames that this item has changed
- sendItems([serializeItem(this)]);
- },
-
- get parentMenu() {
- return internal(this).parentMenu;
- },
-});
-
-function workerMessageReceived(process, id, args) {
- if (internal(this).id != id)
- return;
-
- emit(this, ...JSON.parse(args));
-}
-
-// All things that have a label on the context menu extend this
-var LabelledItem = Class({
- extends: BaseItem,
- implements: [ EventTarget ],
-
- initialize: function initialize(options) {
- BaseItem.prototype.initialize.call(this);
- EventTarget.prototype.initialize.call(this, options);
-
- internal(this).messageListener = workerMessageReceived.bind(this);
- processes.port.on('sdk/worker/event', internal(this).messageListener);
- },
-
- destroy: function destroy() {
- if (internal(this).destroyed)
- return;
-
- processes.port.off('sdk/worker/event', internal(this).messageListener);
-
- BaseItem.prototype.destroy.call(this);
- },
-
- get label() {
- return internal(this).options.label;
- },
-
- set label(val) {
- internal(this).options.label = val;
-
- MenuManager.updateItem(this);
- },
-
- get accesskey() {
- return internal(this).options.accesskey;
- },
-
- set accesskey(val) {
- internal(this).options.accesskey = val;
-
- MenuManager.updateItem(this);
- },
-
- get image() {
- return internal(this).options.image;
- },
-
- set image(val) {
- internal(this).options.image = val;
-
- MenuManager.updateItem(this);
- },
-
- get data() {
- return internal(this).options.data;
- },
-
- set data(val) {
- internal(this).options.data = val;
- }
-});
-
-var Item = Class({
- extends: LabelledItem,
-
- initialize: function initialize(options) {
- internal(this).options = validateOptions(options, itemRules);
-
- LabelledItem.prototype.initialize.call(this, options);
- },
-
- toString: function toString() {
- return "[object Item \"" + this.label + "\"]";
- },
-
- get data() {
- return internal(this).options.data;
- },
-
- set data(val) {
- internal(this).options.data = val;
-
- MenuManager.updateItem(this);
- },
-});
-exports.Item = Item;
-
-var ItemContainer = Class({
- initialize: function initialize() {
- internal(this).children = [];
- },
-
- destroy: function destroy() {
- // Destroys the entire hierarchy
- for (let item of internal(this).children)
- item.destroy();
- },
-
- addItem: function addItem(item) {
- let oldParent = item.parentMenu;
-
- // Don't just call removeItem here as that would remove the corresponding
- // UI element which is more costly than just moving it to the right place
- if (oldParent)
- internal(oldParent).children = removeItemFromArray(internal(oldParent).children, item);
-
- let after = null;
- let children = internal(this).children;
- if (children.length > 0)
- after = children[children.length - 1];
-
- children.push(item);
- internal(item).parentMenu = this;
-
- // If there was an old parent then we just have to move the item, otherwise
- // it needs to be created
- if (oldParent)
- MenuManager.moveItem(item, after);
- else
- MenuManager.createItem(item, after);
- },
-
- removeItem: function removeItem(item) {
- // If the item isn't a child of this menu then ignore this call
- if (item.parentMenu !== this)
- return;
-
- MenuManager.removeItem(item);
-
- internal(this).children = removeItemFromArray(internal(this).children, item);
- internal(item).parentMenu = null;
- },
-
- get items() {
- return internal(this).children.slice(0);
- },
-
- set items(val) {
- // Validate the arguments before making any changes
- if (!Array.isArray(val))
- throw new Error(menuOptionRules.items.msg);
-
- for (let item of val) {
- if (!(item instanceof BaseItem))
- throw new Error(menuOptionRules.items.msg);
- }
-
- // Remove the old items and add the new ones
- for (let item of internal(this).children)
- this.removeItem(item);
-
- for (let item of val)
- this.addItem(item);
- },
-});
-
-var Menu = Class({
- extends: LabelledItem,
- implements: [ItemContainer],
-
- initialize: function initialize(options) {
- internal(this).options = validateOptions(options, menuRules);
-
- LabelledItem.prototype.initialize.call(this, options);
- ItemContainer.prototype.initialize.call(this);
-
- if (internal(this).options.items) {
- for (let item of internal(this).options.items)
- this.addItem(item);
- }
- },
-
- destroy: function destroy() {
- ItemContainer.prototype.destroy.call(this);
- LabelledItem.prototype.destroy.call(this);
- },
-
- toString: function toString() {
- return "[object Menu \"" + this.label + "\"]";
- },
-});
-exports.Menu = Menu;
-
-var Separator = Class({
- extends: BaseItem,
-
- initialize: function initialize(options) {
- internal(this).options = validateOptions(options, baseItemRules);
-
- BaseItem.prototype.initialize.call(this);
- },
-
- toString: function toString() {
- return "[object Separator]";
- }
-});
-exports.Separator = Separator;
-
-// Holds items for the content area context menu
-var contentContextMenu = ItemContainer();
-exports.contentContextMenu = contentContextMenu;
-
-function getContainerItems(container) {
- let items = [];
- for (let item of internal(container).children) {
- items.push(serializeItem(item));
- if (item instanceof Menu)
- items = items.concat(getContainerItems(item));
- }
- return items;
-}
-
-// Notify all frames of these new or changed items
-function sendItems(items) {
- processes.port.emit("sdk/contextmenu/createitems", items);
-}
-
-// Called when a new process is created and needs to get the current list of items
-function remoteItemRequest(process) {
- let items = getContainerItems(contentContextMenu);
- if (items.length == 0)
- return;
-
- process.port.emit("sdk/contextmenu/createitems", items);
-}
-processes.forEvery(remoteItemRequest);
-
-when(function() {
- contentContextMenu.destroy();
-});
-
-// App specific UI code lives here, it should handle populating the context
-// menu and passing clicks etc. through to the items.
-
-function countVisibleItems(nodes) {
- return Array.reduce(nodes, function(sum, node) {
- return node.hidden ? sum : sum + 1;
- }, 0);
-}
-
-var MenuWrapper = Class({
- initialize: function initialize(winWrapper, items, contextMenu) {
- this.winWrapper = winWrapper;
- this.window = winWrapper.window;
- this.items = items;
- this.contextMenu = contextMenu;
- this.populated = false;
- this.menuMap = new Map();
-
- // updateItemVisibilities will run first, updateOverflowState will run after
- // all other instances of this module have run updateItemVisibilities
- this._updateItemVisibilities = this.updateItemVisibilities.bind(this);
- this.contextMenu.addEventListener("popupshowing", this._updateItemVisibilities, true);
- this._updateOverflowState = this.updateOverflowState.bind(this);
- this.contextMenu.addEventListener("popupshowing", this._updateOverflowState);
- },
-
- destroy: function destroy() {
- this.contextMenu.removeEventListener("popupshowing", this._updateOverflowState);
- this.contextMenu.removeEventListener("popupshowing", this._updateItemVisibilities, true);
-
- if (!this.populated)
- return;
-
- // If we're getting unloaded at runtime then we must remove all the
- // generated XUL nodes
- let oldParent = null;
- for (let item of internal(this.items).children) {
- let xulNode = this.getXULNodeForItem(item);
- oldParent = xulNode.parentNode;
- oldParent.removeChild(xulNode);
- }
-
- if (oldParent)
- this.onXULRemoved(oldParent);
- },
-
- get separator() {
- return this.contextMenu.querySelector("." + SEPARATOR_CLASS);
- },
-
- get overflowMenu() {
- return this.contextMenu.querySelector("." + OVERFLOW_MENU_CLASS);
- },
-
- get overflowPopup() {
- return this.contextMenu.querySelector("." + OVERFLOW_POPUP_CLASS);
- },
-
- get topLevelItems() {
- return this.contextMenu.querySelectorAll("." + TOPLEVEL_ITEM_CLASS);
- },
-
- get overflowItems() {
- return this.contextMenu.querySelectorAll("." + OVERFLOW_ITEM_CLASS);
- },
-
- getXULNodeForItem: function getXULNodeForItem(item) {
- return this.menuMap.get(item);
- },
-
- // Recurses through the item hierarchy creating XUL nodes for everything
- populate: function populate(menu) {
- for (let i = 0; i < internal(menu).children.length; i++) {
- let item = internal(menu).children[i];
- let after = i === 0 ? null : internal(menu).children[i - 1];
- this.createItem(item, after);
-
- if (item instanceof Menu)
- this.populate(item);
- }
- },
-
- // Recurses through the menu setting the visibility of items. Returns true
- // if any of the items in this menu were visible
- setVisibility: function setVisibility(menu, addonInfo, usePageWorker) {
- let anyVisible = false;
-
- for (let item of internal(menu).children) {
- let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker);
-
- // Recurse through Menus, if none of the sub-items were visible then the
- // menu is hidden too.
- if (visible && (item instanceof Menu))
- visible = this.setVisibility(item, addonInfo, false);
-
- let xulNode = this.getXULNodeForItem(item);
- xulNode.hidden = !visible;
-
- anyVisible = anyVisible || visible;
- }
-
- return anyVisible;
- },
-
- // Works out where to insert a XUL node for an item in a browser window
- insertIntoXUL: function insertIntoXUL(item, node, after) {
- let menupopup = null;
- let before = null;
-
- let menu = item.parentMenu;
- if (menu === this.items) {
- // Insert into the overflow popup if it exists, otherwise the normal
- // context menu
- menupopup = this.overflowPopup;
- if (!menupopup)
- menupopup = this.contextMenu;
- }
- else {
- let xulNode = this.getXULNodeForItem(menu);
- menupopup = xulNode.firstChild;
- }
-
- if (after) {
- let afterNode = this.getXULNodeForItem(after);
- before = afterNode.nextSibling;
- }
- else if (menupopup === this.contextMenu) {
- let topLevel = this.topLevelItems;
- if (topLevel.length > 0)
- before = topLevel[topLevel.length - 1].nextSibling;
- else
- before = this.separator.nextSibling;
- }
-
- menupopup.insertBefore(node, before);
- },
-
- // Sets the right class for XUL nodes
- updateXULClass: function updateXULClass(xulNode) {
- if (xulNode.parentNode == this.contextMenu)
- xulNode.classList.add(TOPLEVEL_ITEM_CLASS);
- else
- xulNode.classList.remove(TOPLEVEL_ITEM_CLASS);
-
- if (xulNode.parentNode == this.overflowPopup)
- xulNode.classList.add(OVERFLOW_ITEM_CLASS);
- else
- xulNode.classList.remove(OVERFLOW_ITEM_CLASS);
- },
-
- // Creates a XUL node for an item
- createItem: function createItem(item, after) {
- if (!this.populated)
- return;
-
- // Create the separator if it doesn't already exist
- if (!this.separator) {
- let separator = this.window.document.createElement("menuseparator");
- separator.setAttribute("class", SEPARATOR_CLASS);
-
- // Insert before the separator created by the old context-menu if it
- // exists to avoid bug 832401
- let oldSeparator = this.window.document.getElementById("jetpack-context-menu-separator");
- if (oldSeparator && oldSeparator.parentNode != this.contextMenu)
- oldSeparator = null;
- this.contextMenu.insertBefore(separator, oldSeparator);
- }
-
- let type = "menuitem";
- if (item instanceof Menu)
- type = "menu";
- else if (item instanceof Separator)
- type = "menuseparator";
-
- let xulNode = this.window.document.createElement(type);
- xulNode.setAttribute("class", ITEM_CLASS);
- if (item instanceof LabelledItem) {
- xulNode.setAttribute("label", item.label);
- if (item.accesskey)
- xulNode.setAttribute("accesskey", item.accesskey);
- if (item.image) {
- xulNode.setAttribute("image", item.image);
- if (item instanceof Menu)
- xulNode.classList.add("menu-iconic");
- else
- xulNode.classList.add("menuitem-iconic");
- }
- if (item.data)
- xulNode.setAttribute("value", item.data);
-
- let self = this;
- xulNode.addEventListener("command", function(event) {
- // Only care about clicks directly on this item
- if (event.target !== xulNode)
- return;
-
- itemActivated(item, xulNode);
- });
- }
-
- this.insertIntoXUL(item, xulNode, after);
- this.updateXULClass(xulNode);
- xulNode.data = item.data;
-
- if (item instanceof Menu) {
- let menupopup = this.window.document.createElement("menupopup");
- xulNode.appendChild(menupopup);
- }
-
- this.menuMap.set(item, xulNode);
- },
-
- // Updates the XUL node for an item in this window
- updateItem: function updateItem(item) {
- if (!this.populated)
- return;
-
- let xulNode = this.getXULNodeForItem(item);
-
- // TODO figure out why this requires setAttribute
- xulNode.setAttribute("label", item.label);
- xulNode.setAttribute("accesskey", item.accesskey || "");
-
- if (item.image) {
- xulNode.setAttribute("image", item.image);
- if (item instanceof Menu)
- xulNode.classList.add("menu-iconic");
- else
- xulNode.classList.add("menuitem-iconic");
- }
- else {
- xulNode.removeAttribute("image");
- xulNode.classList.remove("menu-iconic");
- xulNode.classList.remove("menuitem-iconic");
- }
-
- if (item.data)
- xulNode.setAttribute("value", item.data);
- else
- xulNode.removeAttribute("value");
- },
-
- // Moves the XUL node for an item in this window to its new place in the
- // hierarchy
- moveItem: function moveItem(item, after) {
- if (!this.populated)
- return;
-
- let xulNode = this.getXULNodeForItem(item);
- let oldParent = xulNode.parentNode;
-
- this.insertIntoXUL(item, xulNode, after);
- this.updateXULClass(xulNode);
- this.onXULRemoved(oldParent);
- },
-
- // Removes the XUL nodes for an item in every window we've ever populated.
- removeItem: function removeItem(item) {
- if (!this.populated)
- return;
-
- let xulItem = this.getXULNodeForItem(item);
-
- let oldParent = xulItem.parentNode;
-
- oldParent.removeChild(xulItem);
- this.menuMap.delete(item);
-
- this.onXULRemoved(oldParent);
- },
-
- // Called when any XUL nodes have been removed from a menupopup. This handles
- // making sure the separator and overflow are correct
- onXULRemoved: function onXULRemoved(parent) {
- if (parent == this.contextMenu) {
- let toplevel = this.topLevelItems;
-
- // If there are no more items then remove the separator
- if (toplevel.length == 0) {
- let separator = this.separator;
- if (separator)
- separator.remove();
- }
- }
- else if (parent == this.overflowPopup) {
- // If there are no more items then remove the overflow menu and separator
- if (parent.childNodes.length == 0) {
- let separator = this.separator;
- separator.remove();
- this.contextMenu.removeChild(parent.parentNode);
- }
- }
- },
-
- // Recurses through all the items owned by this module and sets their hidden
- // state
- updateItemVisibilities: function updateItemVisibilities(event) {
- try {
- if (event.type != "popupshowing")
- return;
- if (event.target != this.contextMenu)
- return;
-
- if (internal(this.items).children.length == 0)
- return;
-
- if (!this.populated) {
- this.populated = true;
- this.populate(this.items);
- }
-
- let mainWindow = event.target.ownerGlobal;
- this.contextMenuContentData = mainWindow.gContextMenuContentData
- if (!(self.id in this.contextMenuContentData.addonInfo)) {
- console.warn("No context menu state data was provided.");
- return;
- }
- let addonInfo = this.contextMenuContentData.addonInfo[self.id];
- lastContextProcessId = addonInfo.processID;
- this.setVisibility(this.items, addonInfo.items, true);
- }
- catch (e) {
- console.exception(e);
- }
- },
-
- // Counts the number of visible items across all modules and makes sure they
- // are in the right place between the top level context menu and the overflow
- // menu
- updateOverflowState: function updateOverflowState(event) {
- try {
- if (event.type != "popupshowing")
- return;
- if (event.target != this.contextMenu)
- return;
-
- // The main items will be in either the top level context menu or the
- // overflow menu at this point. Count the visible ones and if they are in
- // the wrong place move them
- let toplevel = this.topLevelItems;
- let overflow = this.overflowItems;
- let visibleCount = countVisibleItems(toplevel) +
- countVisibleItems(overflow);
-
- if (visibleCount == 0) {
- let separator = this.separator;
- if (separator)
- separator.hidden = true;
- let overflowMenu = this.overflowMenu;
- if (overflowMenu)
- overflowMenu.hidden = true;
- }
- else if (visibleCount > MenuManager.overflowThreshold) {
- this.separator.hidden = false;
- let overflowPopup = this.overflowPopup;
- if (overflowPopup)
- overflowPopup.parentNode.hidden = false;
-
- if (toplevel.length > 0) {
- // The overflow menu shouldn't exist here but let's play it safe
- if (!overflowPopup) {
- let overflowMenu = this.window.document.createElement("menu");
- overflowMenu.setAttribute("class", OVERFLOW_MENU_CLASS);
- overflowMenu.setAttribute("label", OVERFLOW_MENU_LABEL);
- overflowMenu.setAttribute("accesskey", OVERFLOW_MENU_ACCESSKEY);
- this.contextMenu.insertBefore(overflowMenu, this.separator.nextSibling);
-
- overflowPopup = this.window.document.createElement("menupopup");
- overflowPopup.setAttribute("class", OVERFLOW_POPUP_CLASS);
- overflowMenu.appendChild(overflowPopup);
- }
-
- for (let xulNode of toplevel) {
- overflowPopup.appendChild(xulNode);
- this.updateXULClass(xulNode);
- }
- }
- }
- else {
- this.separator.hidden = false;
-
- if (overflow.length > 0) {
- // Move all the overflow nodes out of the overflow menu and position
- // them immediately before it
- for (let xulNode of overflow) {
- this.contextMenu.insertBefore(xulNode, xulNode.parentNode.parentNode);
- this.updateXULClass(xulNode);
- }
- this.contextMenu.removeChild(this.overflowMenu);
- }
- }
- }
- catch (e) {
- console.exception(e);
- }
- }
-});
-
-// This wraps every window that we've seen
-var WindowWrapper = Class({
- initialize: function initialize(window) {
- this.window = window;
- this.menus = [
- new MenuWrapper(this, contentContextMenu, window.document.getElementById("contentAreaContextMenu")),
- ];
- },
-
- destroy: function destroy() {
- for (let menuWrapper of this.menus)
- menuWrapper.destroy();
- },
-
- getMenuWrapperForItem: function getMenuWrapperForItem(item) {
- let root = item.parentMenu;
- while (root.parentMenu)
- root = root.parentMenu;
-
- for (let wrapper of this.menus) {
- if (wrapper.items === root)
- return wrapper;
- }
-
- return null;
- }
-});
-
-var MenuManager = {
- windowMap: new Map(),
-
- get overflowThreshold() {
- let prefs = require("./preferences/service");
- return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
- },
-
- // When a new window is added start watching it for context menu shows
- onTrack: function onTrack(window) {
- if (!isBrowser(window))
- return;
-
- // Generally shouldn't happen, but just in case
- if (this.windowMap.has(window)) {
- console.warn("Already seen this window");
- return;
- }
-
- let winWrapper = WindowWrapper(window);
- this.windowMap.set(window, winWrapper);
- },
-
- onUntrack: function onUntrack(window) {
- if (!isBrowser(window))
- return;
-
- let winWrapper = this.windowMap.get(window);
- // This shouldn't happen but protect against it anyway
- if (!winWrapper)
- return;
- winWrapper.destroy();
-
- this.windowMap.delete(window);
- },
-
- // Creates a XUL node for an item in every window we've already populated
- createItem: function createItem(item, after) {
- for (let [window, winWrapper] of this.windowMap) {
- let menuWrapper = winWrapper.getMenuWrapperForItem(item);
- if (menuWrapper)
- menuWrapper.createItem(item, after);
- }
- },
-
- // Updates the XUL node for an item in every window we've already populated
- updateItem: function updateItem(item) {
- for (let [window, winWrapper] of this.windowMap) {
- let menuWrapper = winWrapper.getMenuWrapperForItem(item);
- if (menuWrapper)
- menuWrapper.updateItem(item);
- }
- },
-
- // Moves the XUL node for an item in every window we've ever populated to its
- // new place in the hierarchy
- moveItem: function moveItem(item, after) {
- for (let [window, winWrapper] of this.windowMap) {
- let menuWrapper = winWrapper.getMenuWrapperForItem(item);
- if (menuWrapper)
- menuWrapper.moveItem(item, after);
- }
- },
-
- // Removes the XUL nodes for an item in every window we've ever populated.
- removeItem: function removeItem(item) {
- for (let [window, winWrapper] of this.windowMap) {
- let menuWrapper = winWrapper.getMenuWrapperForItem(item);
- if (menuWrapper)
- menuWrapper.removeItem(item);
- }
- }
-};
-
-WindowTracker(MenuManager);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/context-menu/context.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/* 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/. */
-
-const { Class } = require("../core/heritage");
-lazyRequire(this, "../util/match-pattern", "MatchPattern");
-const readers = require("./readers");
-
-// Context class is required to implement a single `isCurrent(target)` method
-// that must return boolean value indicating weather given target matches a
-// context or not. Most context implementations below will have an associated
-// reader that way context implementation can setup a reader to extract necessary
-// information to make decision if target is matching a context.
-const Context = Class({
- isRequired: false,
- isCurrent(target) {
- throw Error("Context class must implement isCurrent(target) method");
- },
- get required() {
- Object.defineProperty(this, "required", {
- value: Object.assign(Object.create(Object.getPrototypeOf(this)),
- this,
- {isRequired: true})
- });
- return this.required;
- }
-});
-Context.required = function(...params) {
- return Object.assign(new this(...params), {isRequired: true});
-};
-exports.Context = Context;
-
-
-// Next few context implementations use an associated reader to extract info
-// from the context target and story it to a private symbol associtaed with
-// a context implementation. That way name collisions are avoided while required
-// information is still carried along.
-const isPage = Symbol("context/page?")
-const PageContext = Class({
- extends: Context,
- read: {[isPage]: new readers.isPage()},
- isCurrent: target => target[isPage]
-});
-exports.Page = PageContext;
-
-const isFrame = Symbol("context/frame?");
-const FrameContext = Class({
- extends: Context,
- read: {[isFrame]: new readers.isFrame()},
- isCurrent: target => target[isFrame]
-});
-exports.Frame = FrameContext;
-
-const selection = Symbol("context/selection")
-const SelectionContext = Class({
- read: {[selection]: new readers.Selection()},
- isCurrent: target => !!target[selection]
-});
-exports.Selection = SelectionContext;
-
-const link = Symbol("context/link");
-const LinkContext = Class({
- extends: Context,
- read: {[link]: new readers.LinkURL()},
- isCurrent: target => !!target[link]
-});
-exports.Link = LinkContext;
-
-const isEditable = Symbol("context/editable?")
-const EditableContext = Class({
- extends: Context,
- read: {[isEditable]: new readers.isEditable()},
- isCurrent: target => target[isEditable]
-});
-exports.Editable = EditableContext;
-
-
-const mediaType = Symbol("context/mediaType")
-
-const ImageContext = Class({
- extends: Context,
- read: {[mediaType]: new readers.MediaType()},
- isCurrent: target => target[mediaType] === "image"
-});
-exports.Image = ImageContext;
-
-
-const VideoContext = Class({
- extends: Context,
- read: {[mediaType]: new readers.MediaType()},
- isCurrent: target => target[mediaType] === "video"
-});
-exports.Video = VideoContext;
-
-
-const AudioContext = Class({
- extends: Context,
- read: {[mediaType]: new readers.MediaType()},
- isCurrent: target => target[mediaType] === "audio"
-});
-exports.Audio = AudioContext;
-
-const isSelectorMatch = Symbol("context/selector/mathches?")
-const SelectorContext = Class({
- extends: Context,
- initialize(selector) {
- this.selector = selector;
- // Each instance of selector context will need to store read
- // data into different field, so that case with multilpe selector
- // contexts won't cause a conflicts.
- this[isSelectorMatch] = Symbol(selector);
- this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
- },
- isCurrent(target) {
- return target[this[isSelectorMatch]];
- }
-});
-exports.Selector = SelectorContext;
-
-const url = Symbol("context/url");
-const URLContext = Class({
- extends: Context,
- initialize(pattern) {
- this.pattern = new MatchPattern(pattern);
- },
- read: {[url]: new readers.PageURL()},
- isCurrent(target) {
- return this.pattern.test(target[url]);
- }
-});
-exports.URL = URLContext;
-
-var PredicateContext = Class({
- extends: Context,
- initialize(isMatch) {
- if (typeof(isMatch) !== "function") {
- throw TypeError("Predicate context mus be passed a function");
- }
-
- this.isMatch = isMatch
- },
- isCurrent(target) {
- return this.isMatch(target);
- }
-});
-exports.Predicate = PredicateContext;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/context-menu/core.js
+++ /dev/null
@@ -1,384 +0,0 @@
-/* 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";
-
-const Contexts = require("./context");
-const Readers = require("./readers");
-const Component = require("../ui/component");
-const { Class } = require("../core/heritage");
-const { map, filter, object, reduce, keys, symbols,
- pairs, values, each, some, isEvery, count } = require("../util/sequence");
-const { loadModule } = require("framescript/manager");
-const { Cu, Cc, Ci } = require("chrome");
-const prefs = require("sdk/preferences/service");
-
-const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
- .getService(Ci.nsIMessageListenerManager);
-const preferencesService = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefService).
- getBranch(null);
-
-
-const readTable = Symbol("context-menu/read-table");
-const nameTable = Symbol("context-menu/name-table");
-const onContext = Symbol("context-menu/on-context");
-const isMatching = Symbol("context-menu/matching-handler?");
-
-exports.onContext = onContext;
-exports.readTable = readTable;
-exports.nameTable = nameTable;
-
-
-const propagateOnContext = (item, data) =>
- each(child => child[onContext](data), item.state.children);
-
-const isContextMatch = item => !item[isMatching] || item[isMatching]();
-
-// For whatever reason addWeakMessageListener does not seems to work as our
-// instance seems to dropped even though it's alive. This is simple workaround
-// to avoid dead object excetptions.
-const WeakMessageListener = function(receiver, handler="receiveMessage") {
- this.receiver = receiver
- this.handler = handler
-};
-WeakMessageListener.prototype = {
- constructor: WeakMessageListener,
- receiveMessage(message) {
- if (Cu.isDeadWrapper(this.receiver)) {
- message.target.messageManager.removeMessageListener(message.name, this);
- }
- else {
- this.receiver[this.handler](message);
- }
- }
-};
-
-const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold";
-const onMessage = Symbol("context-menu/message-listener");
-const onPreferceChange = Symbol("context-menu/preference-change");
-const ContextMenuExtension = Class({
- extends: Component,
- initialize: Component,
- setup() {
- const messageListener = new WeakMessageListener(this, onMessage);
- loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame");
- globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener);
- globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener);
-
- preferencesService.addObserver(OVERFLOW_THRESH, this);
- },
- observe(_, __, name) {
- if (name === OVERFLOW_THRESH) {
- const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
- this[Component.patch]({overflowThreshold});
- }
- },
- [onMessage]({name, data, target}) {
- if (name === "sdk/context-menu/read")
- this[onContext]({target, data});
- if (name === "sdk/context-menu/readers?")
- target.messageManager.sendAsyncMessage("sdk/context-menu/readers",
- JSON.parse(JSON.stringify(this.state.readers)));
- },
- [Component.initial](options={}, children) {
- const element = options.element || null;
- const target = options.target || null;
- const readers = Object.create(null);
- const users = Object.create(null);
- const registry = new WeakSet();
- const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
-
- return { target, children: [], readers, users, element,
- registry, overflowThreshold };
- },
- [Component.isUpdated](before, after) {
- // Update only if target changed, since there is no point in re-rendering
- // when children are. Also new items added won't be in sync with a latest
- // context target so we should really just render before drawing context
- // menu.
- return before.target !== after.target;
- },
- [Component.render]({element, children, overflowThreshold}) {
- if (!element) return null;
-
- const items = children.filter(isContextMatch);
- const body = items.length === 0 ? items :
- items.length < overflowThreshold ? [new Separator(),
- ...items] :
- [{tagName: "menu",
- className: "sdk-context-menu-overflow-menu",
- label: "Add-ons",
- accesskey: "A",
- children: [{tagName: "menupopup",
- children: items}]}];
- return {
- element: element,
- tagName: "menugroup",
- style: "-moz-box-orient: vertical;",
- className: "sdk-context-menu-extension",
- children: body
- }
- },
- // Adds / remove child to it's own list.
- add(item) {
- this[Component.patch]({children: this.state.children.concat(item)});
- },
- remove(item) {
- this[Component.patch]({
- children: this.state.children.filter(x => x !== item)
- });
- },
- register(item) {
- const { users, registry } = this.state;
- if (registry.has(item)) return;
- registry.add(item);
-
- // Each (ContextHandler) item has a readTable that is a
- // map of keys to readers extracting them from the content.
- // During the registraction we update intrnal record of unique
- // readers and users per reader. Most context will have a reader
- // shared across all instances there for map of users per reader
- // is stored separately from the reader so that removing reader
- // will occur only when no users remain.
- const table = item[readTable];
- // Context readers store data in private symbols so we need to
- // collect both table keys and private symbols.
- const names = [...keys(table), ...symbols(table)];
- const readers = map(name => table[name], names);
- // Create delta for registered readers that will be merged into
- // internal readers table.
- const added = filter(x => !users[x.id], readers);
- const delta = object(...map(x => [x.id, x], added));
-
- const update = reduce((update, reader) => {
- const n = update[reader.id] || 0;
- update[reader.id] = n + 1;
- return update;
- }, Object.assign({}, users), readers);
-
- // Patch current state with a changes that registered item caused.
- this[Component.patch]({users: update,
- readers: Object.assign(this.state.readers, delta)});
-
- if (count(added)) {
- globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
- JSON.parse(JSON.stringify(delta)));
- }
- },
- unregister(item) {
- const { users, registry } = this.state;
- if (!registry.has(item)) return;
- registry.delete(item);
-
- const table = item[readTable];
- const names = [...keys(table), ...symbols(table)];
- const readers = map(name => table[name], names);
- const update = reduce((update, reader) => {
- update[reader.id] = update[reader.id] - 1;
- return update;
- }, Object.assign({}, users), readers);
- const removed = filter(id => !update[id], keys(update));
- const delta = object(...map(x => [x, null], removed));
-
- this[Component.patch]({users: update,
- readers: Object.assign(this.state.readers, delta)});
-
- if (count(removed)) {
- globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
- JSON.parse(JSON.stringify(delta)));
- }
- },
-
- [onContext]({data, target}) {
- propagateOnContext(this, data);
- const document = target.ownerDocument;
- const element = document.getElementById("contentAreaContextMenu");
-
- this[Component.patch]({target: data, element: element});
- }
-});this,
-exports.ContextMenuExtension = ContextMenuExtension;
-
-// Takes an item options and
-const makeReadTable = ({context, read}) => {
- // Result of this function is a tuple of all readers &
- // name, reader id pairs.
-
- // Filter down to contexts that have a reader associated.
- const contexts = filter(context => context.read, context);
- // Merge all contexts read maps to a single hash, note that there should be
- // no name collisions as context implementations expect to use private
- // symbols for storing it's read data.
- return Object.assign({}, ...map(({read}) => read, contexts), read);
-}
-
-const readTarget = (nameTable, data) =>
- object(...map(([name, id]) => [name, data[id]], nameTable))
-
-const ContextHandler = Class({
- extends: Component,
- initialize: Component,
- get context() {
- return this.state.options.context;
- },
- get read() {
- return this.state.options.read;
- },
- [Component.initial](options) {
- return {
- table: makeReadTable(options),
- requiredContext: filter(context => context.isRequired, options.context),
- optionalContext: filter(context => !context.isRequired, options.context)
- }
- },
- [isMatching]() {
- const {target, requiredContext, optionalContext} = this.state;
- return isEvery(context => context.isCurrent(target), requiredContext) &&
- (count(optionalContext) === 0 ||
- some(context => context.isCurrent(target), optionalContext));
- },
- setup() {
- const table = makeReadTable(this.state.options);
- this[readTable] = table;
- this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)),
- ...map(name => [name, table[name].id], keys(table))];
-
-
- contextMenu.register(this);
-
- each(child => contextMenu.remove(child), this.state.children);
- contextMenu.add(this);
- },
- dispose() {
- contextMenu.remove(this);
-
- each(child => contextMenu.unregister(child), this.state.children);
- contextMenu.unregister(this);
- },
- // Internal `Symbol("onContext")` method is invoked when "contextmenu" event
- // occurs in content process. Context handles with children delegate to each
- // child and patch it's internal state to reflect new contextmenu target.
- [onContext](data) {
- propagateOnContext(this, data);
- this[Component.patch]({target: readTarget(this[nameTable], data)});
- }
-});
-const isContextHandler = item => item instanceof ContextHandler;
-
-exports.ContextHandler = ContextHandler;
-
-const Menu = Class({
- extends: ContextHandler,
- [isMatching]() {
- return ContextHandler.prototype[isMatching].call(this) &&
- this.state.children.filter(isContextHandler)
- .some(isContextMatch);
- },
- [Component.render]({children, options}) {
- const items = children.filter(isContextMatch);
- return {tagName: "menu",
- className: "sdk-context-menu menu-iconic",
- label: options.label,
- accesskey: options.accesskey,
- image: options.icon,
- children: [{tagName: "menupopup",
- children: items}]};
- }
-});
-exports.Menu = Menu;
-
-const onCommand = Symbol("context-menu/item/onCommand");
-const Item = Class({
- extends: ContextHandler,
- get onClick() {
- return this.state.options.onClick;
- },
- [Component.render]({options}) {
- const {label, icon, accesskey} = options;
- return {tagName: "menuitem",
- className: "sdk-context-menu-item menuitem-iconic",
- label,
- accesskey,
- image: icon,
- oncommand: this};
- },
- handleEvent(event) {
- if (this.onClick)
- this.onClick(this.state.target);
- }
-});
-exports.Item = Item;
-
-var Separator = Class({
- extends: Component,
- initialize: Component,
- [Component.render]() {
- return {tagName: "menuseparator",
- className: "sdk-context-menu-separator"}
- },
- [onContext]() {
-
- }
-});
-exports.Separator = Separator;
-
-exports.Contexts = Contexts;
-exports.Readers = Readers;
-
-const createElement = (vnode, {document}) => {
- const node = vnode.namespace ?
- document.createElementNS(vnode.namespace, vnode.tagName) :
- document.createElement(vnode.tagName);
-
- node.setAttribute("data-component-path", vnode[Component.path]);
-
- each(([key, value]) => {
- if (key === "tagName") {
- return;
- }
- if (key === "children") {
- return;
- }
-
- if (key.startsWith("on")) {
- node.addEventListener(key.substr(2), value)
- return;
- }
-
- if (typeof(value) !== "object" &&
- typeof(value) !== "function" &&
- value !== void(0) &&
- value !== null)
- {
- if (key === "className") {
- node[key] = value;
- }
- else {
- node.setAttribute(key, value);
- }
- return;
- }
- }, pairs(vnode));
-
- each(child => node.appendChild(createElement(child, {document})), vnode.children);
- return node;
-};
-
-const htmlWriter = tree => {
- if (tree !== null) {
- const root = tree.element;
- const node = createElement(tree, {document: root.ownerDocument});
- const before = root.querySelector("[data-component-path='/']");
- if (before) {
- root.replaceChild(node, before);
- } else {
- root.appendChild(node);
- }
- }
-};
-
-
-const contextMenu = ContextMenuExtension();
-exports.contextMenu = contextMenu;
-Component.mount(contextMenu, htmlWriter);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/context-menu/readers.js
+++ /dev/null
@@ -1,112 +0,0 @@
-/* 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/. */
-const { Class } = require("../core/heritage");
-const { extend } = require("../util/object");
-const { memoize, method, identity } = require("../lang/functional");
-
-const serializeCategory = ({type}) => ({ category: `reader/${type}()` });
-
-const Reader = Class({
- initialize() {
- this.id = `reader/${this.type}()`
- },
- toJSON() {
- return serializeCategory(this);
- }
-});
-
-
-const MediaTypeReader = Class({ extends: Reader, type: "MediaType" });
-exports.MediaType = MediaTypeReader;
-
-const LinkURLReader = Class({ extends: Reader, type: "LinkURL" });
-exports.LinkURL = LinkURLReader;
-
-const SelectionReader = Class({ extends: Reader, type: "Selection" });
-exports.Selection = SelectionReader;
-
-const isPageReader = Class({ extends: Reader, type: "isPage" });
-exports.isPage = isPageReader;
-
-const isFrameReader = Class({ extends: Reader, type: "isFrame" });
-exports.isFrame = isFrameReader;
-
-const isEditable = Class({ extends: Reader, type: "isEditable"});
-exports.isEditable = isEditable;
-
-
-
-const ParameterizedReader = Class({
- extends: Reader,
- readParameter: function(value) {
- return value;
- },
- toJSON: function() {
- var json = serializeCategory(this);
- json[this.parameter] = this[this.parameter];
- return json;
- },
- initialize(...params) {
- if (params.length) {
- this[this.parameter] = this.readParameter(...params);
- }
- this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`;
- }
-});
-exports.ParameterizedReader = ParameterizedReader;
-
-
-const QueryReader = Class({
- extends: ParameterizedReader,
- type: "Query",
- parameter: "path"
-});
-exports.Query = QueryReader;
-
-
-const AttributeReader = Class({
- extends: ParameterizedReader,
- type: "Attribute",
- parameter: "name"
-});
-exports.Attribute = AttributeReader;
-
-const SrcURLReader = Class({
- extends: AttributeReader,
- name: "src",
-});
-exports.SrcURL = SrcURLReader;
-
-const PageURLReader = Class({
- extends: QueryReader,
- path: "ownerDocument.URL",
-});
-exports.PageURL = PageURLReader;
-
-const SelectorMatchReader = Class({
- extends: ParameterizedReader,
- type: "SelectorMatch",
- parameter: "selector"
-});
-exports.SelectorMatch = SelectorMatchReader;
-
-const extractors = new WeakMap();
-extractors.id = 0;
-
-
-var Extractor = Class({
- extends: ParameterizedReader,
- type: "Extractor",
- parameter: "source",
- initialize: function(f) {
- this[this.parameter] = String(f);
- if (!extractors.has(f)) {
- extractors.id = extractors.id + 1;
- extractors.set(f, extractors.id);
- }
-
- this.id = `reader/${this.type}.for(${extractors.get(f)})`
- }
-});
-exports.Extractor = Extractor;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/context-menu@2.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/* 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";
-
-const shared = require("toolkit/require");
-const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core");
-const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable")
-const { Class } = require("sdk/core/heritage")
-
-const makeDisposable = Type => Class({
- extends: Type,
- implements: [Disposable],
- initialize: Type.prototype.initialize,
- setup(...params) {
- Type.prototype.setup.call(this, ...params);
- setupDisposable(this);
- },
- dispose(...params) {
- disposeDisposable(this);
- Type.prototype.dispose.call(this, ...params);
- }
-});
-
-exports.Separator = Separator;
-exports.Contexts = Contexts;
-exports.Readers = Readers;
-
-// Subclass Item & Menu shared classes so their items
-// will be unloaded when add-on is unloaded.
-exports.Item = makeDisposable(Item);
-exports.Menu = makeDisposable(Menu);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/input/browser.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* 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";
-
-const { windows, isBrowser, isInteractive, isDocumentLoaded,
- getOuterId } = require("../window/utils");
-const { InputPort } = require("./system");
-const { lift, merges, foldp, keepIf, start, Input } = require("../event/utils");
-const { patch } = require("diffpatcher/index");
-const { Sequence, seq, filter, object, pairs } = require("../util/sequence");
-
-
-// Create lazy iterators from the regular arrays, although
-// once https://github.com/mozilla/addon-sdk/pull/1314 lands
-// `windows` will be transforme to lazy iterators.
-// When iterated over belowe sequences items will represent
-// state of windows at the time of iteration.
-const opened = seq(function*() {
- const items = windows("navigator:browser", {includePrivate: true});
- for (let item of items) {
- yield [getOuterId(item), item];
- }
-});
-const interactive = filter(([_, window]) => isInteractive(window), opened);
-const loaded = filter(([_, window]) => isDocumentLoaded(window), opened);
-
-// Helper function that converts given argument to a delta.
-const Update = window => window && object([getOuterId(window), window]);
-const Delete = window => window && object([getOuterId(window), null]);
-
-
-// Signal represents delta for last top level window close.
-const LastClosed = lift(Delete,
- keepIf(isBrowser, null,
- new InputPort({topic: "domwindowclosed"})));
-exports.LastClosed = LastClosed;
-
-const windowFor = document => document && document.defaultView;
-
-// Signal represent delta for last top level window document becoming interactive.
-const InteractiveDoc = new InputPort({topic: "chrome-document-interactive"});
-const InteractiveWin = lift(windowFor, InteractiveDoc);
-const LastInteractive = lift(Update, keepIf(isBrowser, null, InteractiveWin));
-exports.LastInteractive = LastInteractive;
-
-// Signal represent delta for last top level window loaded.
-const LoadedDoc = new InputPort({topic: "chrome-document-loaded"});
-const LoadedWin = lift(windowFor, LoadedDoc);
-const LastLoaded = lift(Update, keepIf(isBrowser, null, LoadedWin));
-exports.LastLoaded = LastLoaded;
-
-
-const initialize = input => {
- if (!input.initialized) {
- input.value = object(...input.value);
- Input.start(input);
- input.initialized = true;
- }
-};
-
-// Signal represents set of top level interactive windows, updated any
-// time new window becomes interactive or one get's closed.
-const Interactive = foldp(patch, interactive, merges([LastInteractive,
- LastClosed]));
-Interactive[start] = initialize;
-exports.Interactive = Interactive;
-
-// Signal represents set of top level loaded window, updated any time
-// new window becomes interactive or one get's closed.
-const Loaded = foldp(patch, loaded, merges([LastLoaded, LastClosed]));
-Loaded[start] = initialize;
-exports.Loaded = Loaded;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/input/customizable-ui.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* 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";
-
-const { Cu } = require("chrome");
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { receive } = require("../event/utils");
-const { InputPort } = require("./system");
-const { object} = require("../util/sequence");
-const { getOuterId } = require("../window/utils");
-
-const Input = function() {};
-Input.prototype = Object.create(InputPort.prototype);
-
-Input.prototype.onCustomizeStart = function (window) {
- receive(this, object([getOuterId(window), true]));
-}
-
-Input.prototype.onCustomizeEnd = function (window) {
- receive(this, object([getOuterId(window), null]));
-}
-
-Input.prototype.addListener = input => CustomizableUI.addListener(input);
-
-Input.prototype.removeListener = input => CustomizableUI.removeListener(input);
-
-exports.CustomizationInput = Input;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/input/frame.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/* 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";
-
-const { Ci } = require("chrome");
-const { InputPort } = require("./system");
-const { getFrameElement, getOuterId,
- getOwnerBrowserWindow } = require("../window/utils");
-const { isnt } = require("../lang/functional");
-const { foldp, lift, merges, keepIf } = require("../event/utils");
-const { object } = require("../util/sequence");
-const { compose } = require("../lang/functional");
-const { LastClosed } = require("./browser");
-const { patch } = require("diffpatcher/index");
-
-const Document = Ci.nsIDOMDocument;
-
-const isntNull = isnt(null);
-
-const frameID = frame => frame.id;
-const browserID = compose(getOuterId, getOwnerBrowserWindow);
-
-const isInnerFrame = frame =>
- frame && frame.hasAttribute("data-is-sdk-inner-frame");
-
-// Utility function that given content window loaded in our frame views returns
-// an actual frame. This basically takes care of fact that actual frame document
-// is loaded in the nested iframe. If content window is not loaded in the nested
-// frame of the frame view it returs null.
-const getFrame = document =>
- document && document.defaultView && getFrameElement(document.defaultView);
-
-const FrameInput = function(options) {
- const input = keepIf(isInnerFrame, null,
- lift(getFrame, new InputPort(options)));
- return lift(frame => {
- if (!frame) return frame;
- const [id, owner] = [frameID(frame), browserID(frame)];
- return object([id, {owners: object([owner, options.update])}]);
- }, input);
-};
-
-const LastLoading = new FrameInput({topic: "document-element-inserted",
- update: {readyState: "loading"}});
-exports.LastLoading = LastLoading;
-
-const LastInteractive = new FrameInput({topic: "content-document-interactive",
- update: {readyState: "interactive"}});
-exports.LastInteractive = LastInteractive;
-
-const LastLoaded = new FrameInput({topic: "content-document-loaded",
- update: {readyState: "complete"}});
-exports.LastLoaded = LastLoaded;
-
-const LastUnloaded = new FrameInput({topic: "content-page-hidden",
- update: null});
-exports.LastUnloaded = LastUnloaded;
-
-// Represents state of SDK frames in form of data structure:
-// {"frame#1": {"id": "frame#1",
-// "inbox": {"data": "ping",
-// "target": {"id": "frame#1", "owner": "outerWindowID#2"},
-// "source": {"id": "frame#1"}}
-// "url": "resource://addon-1/data/index.html",
-// "owners": {"outerWindowID#1": {"readyState": "loading"},
-// "outerWindowID#2": {"readyState": "complete"}}
-//
-//
-// frame#2: {"id": "frame#2",
-// "url": "resource://addon-1/data/main.html",
-// "outbox": {"data": "pong",
-// "source": {"id": "frame#2", "owner": "outerWindowID#1"}
-// "target": {"id": "frame#2"}}
-// "owners": {outerWindowID#1: {readyState: "interacitve"}}}}
-const Frames = foldp(patch, {}, merges([
- LastLoading,
- LastInteractive,
- LastLoaded,
- LastUnloaded,
- new InputPort({ id: "frame-mailbox" }),
- new InputPort({ id: "frame-change" }),
- new InputPort({ id: "frame-changed" })
-]));
-exports.Frames = Frames;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/input/system.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/* 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";
-
-const { Cc, Ci, Cr, Cu } = require("chrome");
-const { Input, start, stop, end, receive, outputs } = require("../event/utils");
-const { once, off } = require("../event/core");
-const { id: addonID } = require("../self");
-
-const unloadMessage = require("@loader/unload");
-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";
-
-const isXrayWrapper = Cu.isXrayWrapper;
-// In the past SDK used to double-wrap notifications dispatched, which
-// made them awkward to use outside of SDK. At present they no longer
-// do that, although we still supported for legacy reasons.
-const isLegacyWrapper = x =>
- x && x.wrappedJSObject &&
- "observersModuleSubjectWrapper" in x.wrappedJSObject;
-
-const unwrapLegacy = x => x.wrappedJSObject.object;
-
-// `InputPort` provides a way to create a signal out of the observer
-// notification subject's for the given `topic`. If `options.initial`
-// is provided it is used as initial value otherwise `null` is used.
-// Constructor can be given `options.id` that will be used to create
-// a `topic` which is namespaced to an add-on (this avoids conflicts
-// when multiple add-on are used, although in a future host probably
-// should just be shared across add-ons). It is also possible to
-// specify a specific `topic` via `options.topic` which is used as
-// without namespacing. Created signal ends whenever add-on is
-// unloaded.
-const InputPort = function InputPort({id, topic, initial}) {
- this.id = id || topic;
- this.topic = topic || "sdk:" + addonID + ":" + id;
- this.value = initial === void(0) ? null : initial;
- this.observing = false;
- this[outputs] = [];
-};
-
-// InputPort type implements `Input` signal interface.
-InputPort.prototype = new Input();
-InputPort.prototype.constructor = InputPort;
-
-// When port is started (which is when it's subgraph get's
-// first subscriber) actual observer is registered.
-InputPort.start = input => {
- input.addListener(input);
- // Also register add-on unload observer to end this signal
- // when that happens.
- addObserver(input, addonUnloadTopic, false);
-};
-InputPort.prototype[start] = InputPort.start;
-
-InputPort.addListener = input => addObserver(input, input.topic, false);
-InputPort.prototype.addListener = InputPort.addListener;
-
-// When port is stopped (which is when it's subgraph has no
-// no subcribers left) an actual observer unregistered.
-// Note that port stopped once it ends as well (which is when
-// add-on is unloaded).
-InputPort.stop = input => {
- input.removeListener(input);
- removeObserver(input, addonUnloadTopic);
-};
-InputPort.prototype[stop] = InputPort.stop;
-
-InputPort.removeListener = input => removeObserver(input, input.topic);
-InputPort.prototype.removeListener = InputPort.removeListener;
-
-// `InputPort` also implements `nsIObserver` interface and
-// `nsISupportsWeakReference` interfaces as it's going to be used as such.
-InputPort.prototype.QueryInterface = function(iid) {
- if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference))
- throw Cr.NS_ERROR_NO_INTERFACE;
-
- return this;
-};
-
-// `InputPort` instances implement `observe` method, which is invoked when
-// observer notifications are dispatched. The `subject` of that notification
-// are received on this signal.
-InputPort.prototype.observe = function(subject, topic, data) {
- // Unwrap message from the subject. SDK used to have it's own version of
- // wrappedJSObjects which take precedence, if subject has `wrappedJSObject`
- // and it's not an XrayWrapper use it as message. Otherwise use subject as
- // is.
- const message = subject === null ? null :
- isLegacyWrapper(subject) ? unwrapLegacy(subject) :
- isXrayWrapper(subject) ? subject :
- subject.wrappedJSObject ? subject.wrappedJSObject :
- subject;
-
- // If observer topic matches topic of the input port receive a message.
- if (topic === this.topic) {
- receive(this, message);
- }
-
- // If observe topic is add-on unload topic we create an end message.
- if (topic === addonUnloadTopic && message === unloadMessage) {
- end(this);
- }
-};
-
-exports.InputPort = InputPort;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/panel.js
+++ /dev/null
@@ -1,436 +0,0 @@
-/* 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";
-
-// The panel module currently supports only Firefox and SeaMonkey.
-// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
-module.metadata = {
- "stability": "stable",
- "engines": {
- "Firefox": "*",
- "SeaMonkey": "*"
- }
-};
-
-const { Cu, Ci } = require("chrome");
-lazyRequire(this, './timers', "setTimeout");
-const { Class } = require("./core/heritage");
-const { DefaultWeakMap, merge } = require("./util/object");
-const { WorkerHost } = require("./content/utils");
-lazyRequire(this, "./deprecated/sync-worker", "Worker");
-const { Disposable } = require("./core/disposable");
-const { WeakReference } = require('./core/reference');
-const { contract: loaderContract } = require("./content/loader");
-const { contract } = require("./util/contract");
-lazyRequire(this, "./event/core", "on", "off", "emit", "setListeners");
-const { EventTarget } = require("./event/target");
-lazyRequireModule(this, "./panel/utils", "domPanel");
-lazyRequire(this, './frame/utils', "getDocShell");
-const { events } = require("./panel/events");
-const { filter, pipe, stripListeners } = require("./event/utils");
-lazyRequire(this, "./view/core", "getNodeView", "getActiveView");
-lazyRequire(this, "./lang/type", "isNil", "isObject", "isNumber");
-lazyRequire(this, "./content/utils", "getAttachEventType");
-const { number, boolean, object } = require('./deprecated/api-utils');
-lazyRequire(this, "./stylesheet/style", "Style");
-lazyRequire(this, "./content/mod", "attach", "detach");
-
-var isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
- some(value => isNumber(value) && !isNaN(value));
-
-var isSDKObj = obj => obj instanceof Class;
-
-var rectContract = contract({
- top: number,
- right: number,
- bottom: number,
- left: number
-});
-
-var position = {
- is: object,
- map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
- ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
- msg: 'The option "position" must be a SDK object registered as anchor; ' +
- 'or an object with one or more of the following keys set to numeric ' +
- 'values: top, right, bottom, left.'
-}
-
-var displayContract = contract({
- width: number,
- height: number,
- focus: boolean,
- position: position
-});
-
-var 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'
- }),
- contextMenu: boolean,
- allow: {
- is: ['object', 'undefined', 'null'],
- map: function (allow) { return { script: !allow || allow.script !== false }}
- },
-}, displayContract.rules, loaderContract.rules));
-
-function Allow(panel) {
- return {
- get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; },
- set script(value) { return setScriptState(panel, value); },
- };
-}
-
-function setScriptState(panel, value) {
- let view = viewFor(panel);
- getDocShell(view.backgroundFrame).allowJavascript = value;
- getDocShell(view.viewFrame).allowJavascript = value;
- view.setAttribute("sdkscriptenabled", "" + value);
-}
-
-function isDisposed(panel) {
- return !views.has(panel);
-}
-
-var optionsMap = new WeakMap();
-var panels = new WeakMap();
-var models = new WeakMap();
-var views = new DefaultWeakMap(panel => {
- let model = models.get(panel);
-
- // Setup view
- let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)};
- let view = domPanel.make(null, viewOptions);
- panels.set(view, panel);
-
- // Load panel content.
- domPanel.setURL(view, model.contentURL);
-
- // Allow context menu
- domPanel.allowContextMenu(view, model.contextMenu);
-
- return view;
-});
-var workers = new DefaultWeakMap(panel => {
- let options = optionsMap.get(panel);
-
- let worker = new Worker(stripListeners(options));
- workers.set(panel, worker);
-
- // pipe events from worker to a panel.
- pipe(worker, panel);
-
- return worker;
-});
-var styles = new WeakMap();
-
-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);
-
-function getPanelFromWeakRef(weakRef) {
- if (!weakRef) {
- return null;
- }
- let panel = weakRef.get();
- if (!panel) {
- return null;
- }
- if (isDisposed(panel)) {
- return null;
- }
- return panel;
-}
-
-var SinglePanelManager = {
- visiblePanel: null,
- enqueuedPanel: null,
- enqueuedPanelCallback: null,
- // Calls |callback| with no arguments when the panel may be shown.
- requestOpen: function(panelToOpen, callback) {
- let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
- if (currentPanel || SinglePanelManager.enqueuedPanel) {
- SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen);
- SinglePanelManager.enqueuedPanelCallback = callback;
- if (currentPanel && currentPanel.isShowing) {
- currentPanel.hide();
- }
- } else {
- SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback);
- }
- },
- notifyPanelCanOpen: function(panel, callback) {
- let view = viewFor(panel);
- // Can't pass an arrow function as the event handler because we need to be
- // able to call |removeEventListener| later.
- view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
- view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown);
- SinglePanelManager.enqueuedPanel = null;
- SinglePanelManager.enqueuedPanelCallback = null;
- SinglePanelManager.visiblePanel = Cu.getWeakReference(panel);
- callback();
- },
- onVisiblePanelShown: function(event) {
- let panel = panelFor(event.target);
- if (SinglePanelManager.enqueuedPanel) {
- // Another panel started waiting for |panel| to close before |panel| was
- // even done opening.
- panel.hide();
- }
- },
- onVisiblePanelHidden: function(event) {
- let view = event.target;
- let panel = panelFor(view);
- let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
- if (currentPanel && currentPanel != panel) {
- return;
- }
- SinglePanelManager.visiblePanel = null;
- view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
- view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown);
- let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel);
- let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback;
- if (nextPanel) {
- SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback);
- }
- }
-};
-
-const Panel = Class({
- implements: [
- // Generate accessors for the validated properties that update model on
- // set and return values from model on get.
- panelContract.properties(modelFor),
- EventTarget,
- Disposable,
- WeakReference
- ],
- extends: WorkerHost(workerFor),
- setup: function setup(options) {
- let model = merge({
- defaultWidth: 320,
- defaultHeight: 240,
- focus: true,
- position: Object.freeze({}),
- contextMenu: false
- }, panelContract(options));
- model.ready = false;
- models.set(this, model);
-
- if (model.contentStyle || model.contentStyleFile) {
- styles.set(this, Style({
- uri: model.contentStyleFile,
- source: model.contentStyle
- }));
- }
-
- optionsMap.set(this, options);
-
- // Setup listeners.
- setListeners(this, options);
- },
- dispose: function dispose() {
- if (views.has(this))
- this.hide();
- off(this);
-
- workerFor(this).destroy();
- detach(styleFor(this));
-
- if (views.has(this))
- domPanel.dispose(viewFor(this));
-
- views.delete(this);
- },
- /* Public API: Panel.width */
- get width() {
- return modelFor(this).width;
- },
- set width(value) {
- this.resize(value, this.height);
- },
- /* Public API: Panel.height */
- get height() {
- return modelFor(this).height;
- },
- set height(value) {
- this.resize(this.width, value);
- },
-
- /* Public API: Panel.focus */
- get focus() {
- return modelFor(this).focus;
- },
-
- /* Public API: Panel.position */
- get position() {
- return modelFor(this).position;
- },
-
- /* Public API: Panel.contextMenu */
- get contextMenu() {
- return modelFor(this).contextMenu;
- },
- set contextMenu(allow) {
- let model = modelFor(this);
- model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
- domPanel.allowContextMenu(viewFor(this), model.contextMenu);
- },
-
- get contentURL() {
- return modelFor(this).contentURL;
- },
- set contentURL(value) {
- let model = modelFor(this);
- model.contentURL = panelContract({ contentURL: value }).contentURL;
- domPanel.setURL(viewFor(this), model.contentURL);
- // Detach worker so that messages send will be queued until it's
- // reatached once panel content is ready.
- workerFor(this).detach();
- },
-
- get allow() { return Allow(this); },
- set allow(value) {
- let allowJavascript = panelContract({ allow: value }).allow.script;
- return setScriptState(this, value);
- },
-
- /* Public API: Panel.isShowing */
- get isShowing() {
- return !isDisposed(this) && domPanel.isOpen(viewFor(this));
- },
-
- /* Public API: Panel.show */
- show: function show(options={}, anchor) {
- let view = viewFor(this);
- SinglePanelManager.requestOpen(this, () => {
- if (options instanceof Ci.nsIDOMElement) {
- [anchor, options] = [options, null];
- }
-
- if (anchor instanceof Ci.nsIDOMElement) {
- console.warn(
- "Passing a DOM node to Panel.show() method is an unsupported " +
- "feature that will be soon replaced. " +
- "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
- );
- }
-
- let model = modelFor(this);
- let anchorView = getNodeView(anchor || options.position || model.position);
-
- options = merge({
- position: model.position,
- width: model.width,
- height: model.height,
- defaultWidth: model.defaultWidth,
- defaultHeight: model.defaultHeight,
- focus: model.focus,
- contextMenu: model.contextMenu
- }, displayContract(options));
-
- if (!isDisposed(this)) {
- domPanel.show(view, options, anchorView);
- }
- });
- return this;
- },
-
- /* Public API: Panel.hide */
- hide: function hide() {
- // Quit immediately if panel is disposed or there is no state change.
- domPanel.close(viewFor(this));
-
- return this;
- },
-
- /* Public API: Panel.resize */
- resize: function resize(width, height) {
- let model = modelFor(this);
- let view = viewFor(this);
- let change = panelContract({
- width: width || model.width || model.defaultWidth,
- height: height || model.height || model.defaultHeight
- });
-
- model.width = change.width
- model.height = change.height
-
- domPanel.resize(view, model.width, model.height);
-
- return this;
- }
-});
-exports.Panel = Panel;
-
-// Note must be defined only after value to `Panel` is assigned.
-getActiveView.define(Panel, viewFor);
-
-// Filter panel events to only panels that are create by this module.
-var panelEvents = filter(events, ({target}) => panelFor(target));
-
-// Panel events emitted after panel has being shown.
-var shows = filter(panelEvents, ({type}) => type === "popupshown");
-
-// Panel events emitted after panel became hidden.
-var hides = filter(panelEvents, ({type}) => type === "popuphidden");
-
-// Panel events emitted after content inside panel is ready. For different
-// panels ready may mean different state based on `contentScriptWhen` attribute.
-// Weather given event represents readyness is detected by `getAttachEventType`
-// helper function.
-var ready = filter(panelEvents, ({type, target}) =>
- getAttachEventType(modelFor(panelFor(target))) === type);
-
-// Panel event emitted when the contents of the panel has been loaded.
-var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded");
-
-// Styles should be always added as soon as possible, and doesn't makes them
-// depends on `contentScriptWhen`
-var start = filter(panelEvents, ({type}) => type === "document-element-inserted");
-
-// Forward panel show / hide events to panel's own event listeners.
-on(shows, "data", ({target}) => {
- let panel = panelFor(target);
- if (modelFor(panel).ready)
- emit(panel, "show");
-});
-
-on(hides, "data", ({target}) => {
- let panel = panelFor(target);
- if (modelFor(panel).ready)
- emit(panel, "hide");
-});
-
-on(ready, "data", ({target}) => {
- let panel = panelFor(target);
- let window = domPanel.getContentDocument(target).defaultView;
-
- workerFor(panel).attach(window);
-});
-
-on(readyToShow, "data", ({target}) => {
- let panel = panelFor(target);
-
- if (!modelFor(panel).ready) {
- modelFor(panel).ready = true;
-
- if (viewFor(panel).state == "open")
- emit(panel, "show");
- }
-});
-
-on(start, "data", ({target}) => {
- let panel = panelFor(target);
- let window = domPanel.getContentDocument(target).defaultView;
-
- attach(styleFor(panel), window);
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/panel/events.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* 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 basically translates system/events to a SDK standard events
-// so that `map`, `filter` and other utilities could be used with them.
-
-module.metadata = {
- "stability": "experimental"
-};
-
-const events = require("../system/events");
-lazyRequire(this, "../event/core", "emit");
-
-var channel = {};
-
-function forward({ subject, type, data }) {
- return emit(channel, "data", { target: subject, type: type, data: data });
-}
-
-["popupshowing", "popuphiding", "popupshown", "popuphidden",
-"document-element-inserted", "DOMContentLoaded", "load"
-].forEach(type => events.on(type, forward));
-
-exports.events = channel;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ /dev/null
@@ -1,451 +0,0 @@
-/* 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 { Cc, Ci } = require("chrome");
-const { Services } = require("resource://gre/modules/Services.jsm");
-lazyRequire(this, "../timers", "setTimeout");
-lazyRequire(this, "../system", "platform");
-lazyRequire(this, "../window/utils", "getMostRecentBrowserWindow", "getOwnerBrowserWindow",
- "getScreenPixelsPerCSSPixel");
-
-lazyRequire(this, "../frame/utils", { "create": "createFrame" }, "swapFrameLoaders", "getDocShell");
-lazyRequire(this, "../addon/window", { "window": "addonWindow" });
-lazyRequire(this, "../lang/type", "isNil");
-lazyRequire(this, '../self', "data");
-
-lazyRequireModule(this, "../system/events", "events");
-
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
- position = position || {};
-
- let x, y;
-
- let hasTop = !isNil(position.top);
- let hasRight = !isNil(position.right);
- let hasBottom = !isNil(position.bottom);
- let hasLeft = !isNil(position.left);
- let hasWidth = !isNil(width);
- let hasHeight = !isNil(height);
-
- // if width is not specified by constructor or show's options, then get
- // the default width
- if (!hasWidth)
- width = defaultWidth;
-
- // if height is not specified by constructor or show's options, then get
- // the default height
- if (!hasHeight)
- height = defaultHeight;
-
- // default position is centered
- x = (rect.right - width) / 2;
- y = (rect.top + rect.bottom - height) / 2;
-
- if (hasTop) {
- y = rect.top + position.top;
-
- if (hasBottom && !hasHeight)
- height = rect.bottom - position.bottom - y;
- }
- else if (hasBottom) {
- y = rect.bottom - position.bottom - height;
- }
-
- if (hasLeft) {
- x = position.left;
-
- if (hasRight && !hasWidth)
- width = rect.right - position.right - x;
- }
- else if (hasRight) {
- x = rect.right - width - position.right;
- }
-
- return {x: x, y: y, width: width, height: height};
-}
-
-function open(panel, options, anchor) {
- // Wait for the XBL binding to be constructed
- if (!panel.openPopup) setTimeout(open, 50, panel, options, anchor);
- else display(panel, options, anchor);
-}
-exports.open = open;
-
-function isOpen(panel) {
- return panel.state === "open"
-}
-exports.isOpen = isOpen;
-
-function isOpening(panel) {
- return panel.state === "showing"
-}
-exports.isOpening = isOpening
-
-function close(panel) {
- // Sometimes "TypeError: panel.hidePopup is not a function" is thrown
- // when quitting the host application while a panel is visible. To suppress
- // these errors, check for "hidePopup" in panel before calling it.
- // It's not clear if there's an issue or it's expected behavior.
- // See Bug 1151796.
-
- return panel.hidePopup && panel.hidePopup();
-}
-exports.close = close
-
-
-function resize(panel, width, height) {
- // Resize the iframe instead of using panel.sizeTo
- // because sizeTo doesn't work with arrow panels
- if (panel.firstChild) {
- panel.firstChild.style.width = width + "px";
- panel.firstChild.style.height = height + "px";
- }
-}
-exports.resize = resize
-
-function display(panel, options, anchor) {
- let document = panel.ownerDocument;
-
- let x, y;
- let { width, height, defaultWidth, defaultHeight } = options;
-
- let popupPosition = null;
-
- // Panel XBL has some SDK incompatible styling decisions. We shim panel
- // instances until proper fix for Bug 859504 is shipped.
- shimDefaultStyle(panel);
-
- if (!anchor) {
- // The XUL Panel doesn't have an arrow, so the margin needs to be reset
- // in order to, be positioned properly
- panel.style.margin = "0";
-
- let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
-
- ({x, y, width, height} = calculateRegion(options, viewportRect));
- }
- else {
- // The XUL Panel has an arrow, so the margin needs to be reset
- // to the default value.
- panel.style.margin = "";
- let { CustomizableUI, window } = anchor.ownerGlobal;
-
- // In Australis, widgets may be positioned in an overflow panel or the
- // menu panel.
- // In such cases clicking this widget will hide the overflow/menu panel,
- // and the widget's panel will show instead.
- // If `CustomizableUI` is not available, it means the anchor is not in a
- // chrome browser window, and therefore there is no need for this check.
- if (CustomizableUI) {
- let node = anchor;
- ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window));
-
- // if `node` is not the `anchor` itself, it means the widget is
- // positioned in a panel, therefore we have to hide it before show
- // the widget's panel in the same anchor
- if (node !== anchor)
- CustomizableUI.hidePanelForNode(anchor);
- }
-
- width = width || defaultWidth;
- height = height || defaultHeight;
-
- // Open the popup by the anchor.
- let rect = anchor.getBoundingClientRect();
-
- let zoom = getScreenPixelsPerCSSPixel(window);
- let screenX = rect.left + window.mozInnerScreenX * zoom;
- let screenY = rect.top + window.mozInnerScreenY * zoom;
-
- // Set up the vertical position of the popup relative to the anchor
- // (always display the arrow on anchor center)
- let horizontal, vertical;
- if (screenY > window.screen.availHeight / 2 + height)
- vertical = "top";
- else
- vertical = "bottom";
-
- if (screenY > window.screen.availWidth / 2 + width)
- horizontal = "left";
- else
- horizontal = "right";
-
- let verticalInverse = vertical == "top" ? "bottom" : "top";
- popupPosition = vertical + "center " + verticalInverse + horizontal;
-
- // Allow panel to flip itself if the panel can't be displayed at the
- // specified position (useful if we compute a bad position or if the
- // user moves the window and panel remains visible)
- panel.setAttribute("flip", "both");
- }
-
- if (!panel.viewFrame) {
- panel.viewFrame = document.importNode(panel.backgroundFrame, false);
- panel.appendChild(panel.viewFrame);
-
- let {privateBrowsingId} = getDocShell(panel.viewFrame).getOriginAttributes();
- let principal = Services.scriptSecurityManager.createNullPrincipal({privateBrowsingId});
- getDocShell(panel.viewFrame).createAboutBlankContentViewer(principal);
- }
-
- // Resize the iframe instead of using panel.sizeTo
- // because sizeTo doesn't work with arrow panels
- panel.firstChild.style.width = width + "px";
- panel.firstChild.style.height = height + "px";
-
- panel.openPopup(anchor, popupPosition, x, y);
-}
-exports.display = display;
-
-// This utility function is just a workaround until Bug 859504 has shipped.
-function shimDefaultStyle(panel) {
- let document = panel.ownerDocument;
- // Please note that `panel` needs to be part of document in order to reach
- // it's anonymous nodes. One of the anonymous node has a big padding which
- // doesn't work well since panel frame needs to fill all of the panel.
- // XBL binding is a not the best option as it's applied asynchronously, and
- // makes injected frames behave in strange way. Also this feels a lot
- // cheaper to do.
- ["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) {
- let node = document.getAnonymousElementByAttribute(panel, "class", value);
- if (node) node.style.padding = 0;
- });
-}
-
-function show(panel, options, anchor) {
- // Prevent the panel from getting focus when showing up
- // if focus is set to false
- panel.setAttribute("noautofocus", !options.focus);
-
- let window = anchor && getOwnerBrowserWindow(anchor);
- let { document } = window ? window : getMostRecentBrowserWindow();
- attach(panel, document);
-
- open(panel, options, anchor);
-}
-exports.show = show
-
-function onPanelClick(event) {
- let { target, metaKey, ctrlKey, shiftKey, button } = event;
- let accel = platform === "darwin" ? metaKey : ctrlKey;
- let isLeftClick = button === 0;
- let isMiddleClick = button === 1;
-
- if ((isLeftClick && (accel || shiftKey)) || isMiddleClick) {
- let link = target.closest('a');
-
- if (link && link.href)
- getMostRecentBrowserWindow().openUILink(link.href, event)
- }
-}
-
-function setupPanelFrame(frame) {
- frame.setAttribute("flex", 1);
- frame.setAttribute("transparent", "transparent");
- frame.setAttribute("autocompleteenabled", true);
- frame.setAttribute("tooltip", "aHTMLTooltip");
- if (platform === "darwin") {
- frame.style.borderRadius = "var(--arrowpanel-border-radius, 3.5px)";
- frame.style.padding = "1px";
- }
-}
-
-function make(document, options) {
- document = document || getMostRecentBrowserWindow().document;
- let panel = document.createElementNS(XUL_NS, "panel");
- panel.setAttribute("type", "arrow");
- panel.setAttribute("sdkscriptenabled", options.allowJavascript);
-
- // The panel needs to be attached to a browser window in order for us
- // to copy browser styles to the content document when it loads.
- attach(panel, document);
-
- let frameOptions = {
- allowJavascript: options.allowJavascript,
- allowPlugins: true,
- allowAuth: true,
- allowWindowControl: false,
- // Need to override `nodeName` to use `iframe` as `browsers` save session
- // history and in consequence do not dispatch "inner-window-destroyed"
- // notifications.
- browser: false,
- };
-
- let backgroundFrame = createFrame(addonWindow, frameOptions);
- setupPanelFrame(backgroundFrame);
-
- getDocShell(backgroundFrame).inheritPrivateBrowsingId = false;
-
- function onPopupShowing({type, target}) {
- if (target === this) {
- let attrs = getDocShell(backgroundFrame).getOriginAttributes();
- getDocShell(panel.viewFrame).setOriginAttributes(attrs);
-
- swapFrameLoaders(backgroundFrame, panel.viewFrame);
- }
- }
-
- function onPopupHiding({type, target}) {
- if (target === this) {
- swapFrameLoaders(backgroundFrame, panel.viewFrame);
-
- panel.viewFrame.remove();
- panel.viewFrame = null;
- }
- }
-
- function onContentReady({target, type}) {
- if (target === getContentDocument(panel)) {
- style(panel);
- events.emit(type, { subject: panel });
- }
- }
-
- function onContentLoad({target, type}) {
- if (target === getContentDocument(panel))
- events.emit(type, { subject: panel });
- }
-
- function onContentChange({subject: document, type}) {
- if (document === getContentDocument(panel) && document.defaultView)
- events.emit(type, { subject: panel });
- }
-
- function onPanelStateChange({target, type}) {
- if (target === this)
- events.emit(type, { subject: panel })
- }
-
- panel.addEventListener("popupshowing", onPopupShowing);
- panel.addEventListener("popuphiding", onPopupHiding);
- for (let event of ["popupshowing", "popuphiding", "popupshown", "popuphidden"])
- panel.addEventListener(event, onPanelStateChange);
-
- panel.addEventListener("click", onPanelClick);
-
- // Panel content document can be either in panel `viewFrame` or in
- // a `backgroundFrame` depending on panel state. Listeners are set
- // on both to avoid setting and removing listeners on panel state changes.
-
- panel.addEventListener("DOMContentLoaded", onContentReady, true);
- backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);
-
- panel.addEventListener("load", onContentLoad, true);
- backgroundFrame.addEventListener("load", onContentLoad, true);
-
- events.on("document-element-inserted", onContentChange);
-
- panel.backgroundFrame = backgroundFrame;
- panel.viewFrame = null;
-
- // Store event listener on the panel instance so that it won't be GC-ed
- // while panel is alive.
- panel.onContentChange = onContentChange;
-
- return panel;
-}
-exports.make = make;
-
-function attach(panel, document) {
- document = document || getMostRecentBrowserWindow().document;
- let container = document.getElementById("mainPopupSet");
- if (container !== panel.parentNode) {
- detach(panel);
- document.getElementById("mainPopupSet").appendChild(panel);
- }
-}
-exports.attach = attach;
-
-function detach(panel) {
- if (panel.parentNode) panel.remove();
-}
-exports.detach = detach;
-
-function dispose(panel) {
- panel.backgroundFrame.remove();
- panel.backgroundFrame = null;
- events.off("document-element-inserted", panel.onContentChange);
- panel.onContentChange = null;
- detach(panel);
-}
-exports.dispose = dispose;
-
-function style(panel) {
- /**
- Injects default OS specific panel styles into content document that is loaded
- into given panel. Optionally `document` of the browser window can be
- given to inherit styles from it, by default it will use either panel owner
- document or an active browser's document. It should not matter though unless
- Firefox decides to style windows differently base on profile or mode like
- chrome for example.
- **/
-
- try {
- let document = panel.ownerDocument;
- let contentDocument = getContentDocument(panel);
- let window = document.defaultView;
- let node = document.getAnonymousElementByAttribute(panel, "class",
- "panel-arrowcontent");
-
- let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
-
- let style = contentDocument.createElement("style");
- style.id = "sdk-panel-style";
- style.textContent = "body { " +
- "color: " + color + ";" +
- "font-family: " + fontFamily + ";" +
- "font-weight: " + fontWeight + ";" +
- "font-size: " + fontSize + ";" +
- "}";
-
- let container = contentDocument.head ? contentDocument.head :
- contentDocument.documentElement;
-
- if (container.firstChild)
- container.insertBefore(style, container.firstChild);
- else
- container.appendChild(style);
- }
- catch (error) {
- console.error("Unable to apply panel style");
- console.exception(error);
- }
-}
-exports.style = style;
-
-var getContentFrame = panel => panel.viewFrame || panel.backgroundFrame;
-exports.getContentFrame = getContentFrame;
-
-function getContentDocument(panel) {
- return getContentFrame(panel).contentDocument;
-}
-exports.getContentDocument = getContentDocument;
-
-function setURL(panel, url) {
- let frame = getContentFrame(panel);
- let webNav = getDocShell(frame).QueryInterface(Ci.nsIWebNavigation);
-
- webNav.loadURI(url ? data.url(url) : "about:blank", 0, null, null, null);
-}
-
-exports.setURL = setURL;
-
-function allowContextMenu(panel, allow) {
- if (allow) {
- panel.setAttribute("context", "contentAreaContextMenu");
- }
- else {
- panel.removeAttribute("context");
- }
-}
-exports.allowContextMenu = allowContextMenu;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-lazyRequire(this, './ui/button/action', 'ActionButton');
-lazyRequire(this, './ui/button/toggle', 'ToggleButton');
-lazyRequire(this, './ui/sidebar', 'Sidebar');
-lazyRequire(this, './ui/frame', 'Frame');
-lazyRequire(this, './ui/toolbar', 'Toolbar');
-
-module.exports = Object.freeze({
- get ActionButton() { return ActionButton; },
- get ToggleButton() { return ToggleButton; },
- get Sidebar() { return Sidebar; },
- get Frame() { return Frame; },
- get Toolbar() { return Toolbar; },
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/button/action.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-const { Class } = require('../../core/heritage');
-const { merge } = require('../../util/object');
-const { Disposable } = require('../../core/disposable');
-lazyRequire(this, '../../event/core', "on", "off", "emit", "setListeners");
-const { EventTarget } = require('../../event/target');
-lazyRequire(this, '../../view/core', "getNodeView");
-
-lazyRequireModule(this, './view', "view");
-const { buttonContract, stateContract } = require('./contract');
-lazyRequire(this, '../state', "properties", "render", "state", "register",
- "unregister", "getDerivedStateFor");
-lazyRequire(this, '../state/events', { "events": "stateEvents" });
-lazyRequire(this, './view/events', { "events": "viewEvents" });
-lazyRequireModule(this, '../../event/utils', "events");
-
-lazyRequire(this, '../../tabs/utils', "getActiveTab");
-
-lazyRequire(this, '../../self', { "id": "addonID" });
-lazyRequire(this, '../id', "identify");
-
-const buttons = new Map();
-
-const toWidgetId = id =>
- ('action-button--' + addonID.toLowerCase()+ '-' + id).
- replace(/[^a-z0-9_-]/g, '');
-
-const ActionButton = Class({
- extends: EventTarget,
- implements: [
- properties(stateContract),
- state(stateContract),
- Disposable
- ],
- setup: function setup(options) {
- let state = merge({
- disabled: false
- }, buttonContract(options));
-
- let id = toWidgetId(options.id);
-
- register(this, state);
-
- // Setup listeners.
- setListeners(this, options);
-
- buttons.set(id, this);
-
- view.create(merge({}, state, { id: id }));
- },
-
- dispose: function dispose() {
- let id = toWidgetId(this.id);
- buttons.delete(id);
-
- off(this);
-
- view.dispose(id);
-
- unregister(this);
- },
-
- get id() {
- return this.state().id;
- },
-
- click: function click() { view.click(toWidgetId(this.id)) }
-});
-exports.ActionButton = ActionButton;
-
-identify.define(ActionButton, ({id}) => toWidgetId(id));
-
-getNodeView.define(ActionButton, button =>
- view.nodeFor(toWidgetId(button.id))
-);
-
-var actionButtonStateEvents = events.filter(stateEvents,
- e => e.target instanceof ActionButton);
-
-var actionButtonViewEvents = events.filter(viewEvents,
- e => buttons.has(e.target));
-
-var clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click');
-var updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update');
-
-on(clickEvents, 'data', ({target: id, window}) => {
- let button = buttons.get(id);
- let state = getDerivedStateFor(button, getActiveTab(window));
-
- emit(button, 'click', state);
-});
-
-on(updateEvents, 'data', ({target: id, window}) => {
- render(buttons.get(id), window);
-});
-
-on(actionButtonStateEvents, 'data', ({target, window, state}) => {
- let id = toWidgetId(target.id);
- view.setIcon(id, window, state.icon);
- view.setLabel(id, window, state.label);
- view.setDisabled(id, window, state.disabled);
- view.setBadge(id, window, state.badge, state.badgeColor);
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/button/contract.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* 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';
-
-const { contract } = require('../../util/contract');
-lazyRequire(this, '../../url', "isLocalURL");
-lazyRequire(this, '../../lang/type', "isNil", "isObject", "isString");
-const { required, either, string, boolean, object, number } = require('../../deprecated/api-utils');
-const { merge } = require('../../util/object');
-const { freeze } = Object;
-
-const isIconSet = (icons) =>
- Object.keys(icons).
- every(size => String(size >>> 0) === size && isLocalURL(icons[size]));
-
-var iconSet = {
- is: either(object, string),
- map: v => isObject(v) ? freeze(merge({}, v)) : v,
- ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
- msg: 'The option "icon" must be a local URL or an object with ' +
- 'numeric keys / local URL values pair.'
-}
-
-var id = {
- is: string,
- ok: v => /^[a-z-_][a-z0-9-_]*$/i.test(v),
- msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
- 'underscores are allowed).'
-};
-
-var label = {
- is: string,
- ok: v => isNil(v) || v.trim().length > 0,
- msg: 'The option "label" must be a non empty string'
-}
-
-var badge = {
- is: either(string, number),
- msg: 'The option "badge" must be a string or a number'
-}
-
-var badgeColor = {
- is: string,
- msg: 'The option "badgeColor" must be a string'
-}
-
-var stateContract = contract({
- label: label,
- icon: iconSet,
- disabled: boolean,
- badge: badge,
- badgeColor: badgeColor
-});
-
-exports.stateContract = stateContract;
-
-var buttonContract = contract(merge({}, stateContract.rules, {
- id: required(id),
- label: required(label),
- icon: required(iconSet)
-}));
-
-exports.buttonContract = buttonContract;
-
-exports.toggleStateContract = contract(merge({
- checked: boolean
-}, stateContract.rules));
-
-exports.toggleButtonContract = contract(merge({
- checked: boolean
-}, buttonContract.rules));
-
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/button/toggle.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-const { Class } = require('../../core/heritage');
-lazyRequire(this, '../../util/object', "merge");
-const { Disposable } = require('../../core/disposable');
-lazyRequire(this, '../../event/core', "on", "off", "emit", "setListeners");
-const { EventTarget } = require('../../event/target');
-lazyRequire(this, '../../view/core', "getNodeView");
-
-lazyRequireModule(this, "./view", "view");
-const { toggleButtonContract, toggleStateContract } = require('./contract');
-lazyRequire(this, '../state', "properties", "render", "state", "register", "unregister",
- "setStateFor", "getStateFor", "getDerivedStateFor");
-lazyRequire(this, '../state/events', { "events": "stateEvents" });
-lazyRequire(this, './view/events', { "events": "viewEvents" });
-lazyRequireModule(this, '../../event/utils', "events");
-
-lazyRequire(this, '../../tabs/utils', "getActiveTab");
-
-lazyRequire(this, '../../self', { "id": "addonID" });
-lazyRequire(this, '../id', "identify");
-
-const buttons = new Map();
-
-const toWidgetId = id =>
- ('toggle-button--' + addonID.toLowerCase()+ '-' + id).
- replace(/[^a-z0-9_-]/g, '');
-
-const ToggleButton = Class({
- extends: EventTarget,
- implements: [
- properties(toggleStateContract),
- state(toggleStateContract),
- Disposable
- ],
- setup: function setup(options) {
- let state = merge({
- disabled: false,
- checked: false
- }, toggleButtonContract(options));
-
- let id = toWidgetId(options.id);
-
- register(this, state);
-
- // Setup listeners.
- setListeners(this, options);
-
- buttons.set(id, this);
-
- view.create(merge({ type: 'checkbox' }, state, { id: id }));
- },
-
- dispose: function dispose() {
- let id = toWidgetId(this.id);
- buttons.delete(id);
-
- off(this);
-
- view.dispose(id);
-
- unregister(this);
- },
-
- get id() {
- return this.state().id;
- },
-
- click: function click() {
- return view.click(toWidgetId(this.id));
- }
-});
-exports.ToggleButton = ToggleButton;
-
-identify.define(ToggleButton, ({id}) => toWidgetId(id));
-
-getNodeView.define(ToggleButton, button =>
- view.nodeFor(toWidgetId(button.id))
-);
-
-var toggleButtonStateEvents = events.filter(stateEvents,
- e => e.target instanceof ToggleButton);
-
-var toggleButtonViewEvents = events.filter(viewEvents,
- e => buttons.has(e.target));
-
-var clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click');
-var updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update');
-
-on(toggleButtonStateEvents, 'data', ({target, window, state}) => {
- let id = toWidgetId(target.id);
-
- view.setIcon(id, window, state.icon);
- view.setLabel(id, window, state.label);
- view.setDisabled(id, window, state.disabled);
- view.setChecked(id, window, state.checked);
- view.setBadge(id, window, state.badge, state.badgeColor);
-});
-
-on(clickEvents, 'data', ({target: id, window, checked }) => {
- let button = buttons.get(id);
- let windowState = getStateFor(button, window);
-
- let newWindowState = merge({}, windowState, { checked: checked });
-
- setStateFor(button, window, newWindowState);
-
- let state = getDerivedStateFor(button, getActiveTab(window));
-
- emit(button, 'click', state);
-
- emit(button, 'change', state);
-});
-
-on(updateEvents, 'data', ({target: id, window}) => {
- render(buttons.get(id), window);
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/button/view.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-const { Cu } = require('chrome');
-lazyRequire(this, '../../event/core', "on", "off", "emit");
-
-lazyRequire(this, 'sdk/self', "data");
-
-lazyRequire(this, '../../lang/type', "isObject", "isNil");
-
-lazyRequire(this, '../../window/utils', "getMostRecentBrowserWindow");
-lazyRequire(this, '../../private-browsing/utils', "ignoreWindow");
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;
-
-lazyRequire(this, './view/events', { "events": "viewEvents" });
-
-const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
-
-const views = new Map();
-const customizedWindows = new WeakMap();
-
-const buttonListener = {
- onCustomizeStart: window => {
- for (let [id, view] of views) {
- setIcon(id, window, view.icon);
- setLabel(id, window, view.label);
- }
-
- customizedWindows.set(window, true);
- },
- onCustomizeEnd: window => {
- customizedWindows.delete(window);
-
- for (let [id, ] of views) {
- let placement = CustomizableUI.getPlacementOfWidget(id);
-
- if (placement)
- emit(viewEvents, 'data', { type: 'update', target: id, window: window });
- }
- },
- onWidgetAfterDOMChange: (node, nextNode, container) => {
- let { id } = node;
- let view = views.get(id);
- let window = node.ownerGlobal;
-
- if (view) {
- emit(viewEvents, 'data', { type: 'update', target: id, window: window });
- }
- }
-};
-
-CustomizableUI.addListener(buttonListener);
-
-require('../../system/unload').when( _ =>
- CustomizableUI.removeListener(buttonListener)
-);
-
-function getNode(id, window) {
- let view = views.get(id);
- return view && view.nodes.get(window);
-};
-
-function isInToolbar(id) {
- let placement = CustomizableUI.getPlacementOfWidget(id);
-
- return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar';
-}
-
-
-function getImage(icon, isInToolbar, pixelRatio) {
- let targetSize = (isInToolbar ? 18 : 32) * pixelRatio;
- let bestSize = 0;
- let image = icon;
-
- if (isObject(icon)) {
- for (let size of Object.keys(icon)) {
- size = +size;
- let offset = targetSize - size;
-
- if (offset === 0) {
- bestSize = size;
- break;
- }
-
- let delta = Math.abs(offset) - Math.abs(targetSize - bestSize);
-
- if (delta < 0)
- bestSize = size;
- }
-
- image = icon[bestSize];
- }
-
- if (image.indexOf('./') === 0)
- return data.url(image.substr(2));
-
- return image;
-}
-
-function nodeFor(id, window=getMostRecentBrowserWindow()) {
- return customizedWindows.has(window) ? null : getNode(id, window);
-};
-exports.nodeFor = nodeFor;
-
-function create(options) {
- let { id, label, icon, type, badge } = options;
-
- if (views.has(id))
- throw new Error('The ID "' + id + '" seems already used.');
-
- CustomizableUI.createWidget({
- id: id,
- type: 'custom',
- removable: true,
- defaultArea: AREA_NAVBAR,
- allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],
-
- onBuild: function(document) {
- let window = document.defaultView;
-
- let node = document.createElementNS(XUL_NS, 'toolbarbutton');
-
- let image = getImage(icon, true, window.devicePixelRatio);
-
- node.setAttribute('id', this.id);
- node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button');
- node.setAttribute('type', type);
- node.setAttribute('label', label);
- node.setAttribute('tooltiptext', label);
- node.setAttribute('image', image);
- node.setAttribute('constrain-size', 'true');
-
- if (!views.get(id)) {
- views.set(id, {
- nodes: new WeakMap(),
- });
- }
-
- let view = views.get(id);
- Object.assign(view, {
- area: this.currentArea,
- icon: icon,
- label: label
- });
-
- if (ignoreWindow(window))
- node.style.display = 'none';
- else
- view.nodes.set(window, node);
-
- node.addEventListener('command', function(event) {
- if (views.has(id)) {
- emit(viewEvents, 'data', {
- type: 'click',
- target: id,
- window: event.view,
- checked: node.checked
- });
- }
- });
-
- return node;
- }
- });
-};
-exports.create = create;
-
-function dispose(id) {
- if (!views.has(id)) return;
-
- views.delete(id);
- CustomizableUI.destroyWidget(id);
-}
-exports.dispose = dispose;
-
-function setIcon(id, window, icon) {
- let node = getNode(id, window);
-
- if (node) {
- icon = customizedWindows.has(window) ? views.get(id).icon : icon;
- let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);
-
- node.setAttribute('image', image);
- }
-}
-exports.setIcon = setIcon;
-
-function setLabel(id, window, label) {
- let node = nodeFor(id, window);
-
- if (node) {
- node.setAttribute('label', label);
- node.setAttribute('tooltiptext', label);
- }
-}
-exports.setLabel = setLabel;
-
-function setDisabled(id, window, disabled) {
- let node = nodeFor(id, window);
-
- if (node)
- node.disabled = disabled;
-}
-exports.setDisabled = setDisabled;
-
-function setChecked(id, window, checked) {
- let node = nodeFor(id, window);
-
- if (node)
- node.checked = checked;
-}
-exports.setChecked = setChecked;
-
-function setBadge(id, window, badge, color) {
- let node = nodeFor(id, window);
-
- if (node) {
- // `Array.from` is needed to handle unicode symbol properly:
- // '𝐀𝐁'.length is 4 where Array.from('𝐀𝐁').length is 2
- let text = badge == null
- ? ''
- : Array.from(String(badge)).slice(0, 4).join('');
-
- node.setAttribute('badge', text);
-
- let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
- 'class', 'toolbarbutton-badge');
-
- if (badgeNode)
- badgeNode.style.backgroundColor = color == null ? '' : color;
- }
-}
-exports.setBadge = setBadge;
-
-function click(id) {
- let node = nodeFor(id);
-
- if (node)
- node.click();
-}
-exports.click = click;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/button/view/events.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '*',
- 'SeaMonkey': '*',
- 'Thunderbird': '*'
- }
-};
-
-var channel = {};
-
-exports.events = channel;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/component.js
+++ /dev/null
@@ -1,182 +0,0 @@
-/* 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";
-
-// Internal properties not exposed to the public.
-const cache = Symbol("component/cache");
-const writer = Symbol("component/writer");
-const isFirstWrite = Symbol("component/writer/first-write?");
-const currentState = Symbol("component/state/current");
-const pendingState = Symbol("component/state/pending");
-const isWriting = Symbol("component/writing?");
-
-const isntNull = x => x !== null;
-
-const Component = function(options, children) {
- this[currentState] = null;
- this[pendingState] = null;
- this[writer] = null;
- this[cache] = null;
- this[isFirstWrite] = true;
-
- this[Component.construct](options, children);
-}
-Component.Component = Component;
-// Constructs component.
-Component.construct = Symbol("component/construct");
-// Called with `options` and `children` and must return
-// initial state back.
-Component.initial = Symbol("component/initial");
-
-// Function patches current `state` with a given update.
-Component.patch = Symbol("component/patch");
-// Function that replaces current `state` with a passed state.
-Component.reset = Symbol("component/reset");
-
-// Function that must return render tree from passed state.
-Component.render = Symbol("component/render");
-
-// Path of the component with in the mount point.
-Component.path = Symbol("component/path");
-
-Component.isMounted = component => !!component[writer];
-Component.isWriting = component => !!component[isWriting];
-
-// Internal method that mounts component to a writer.
-// Mounts component to a writer.
-Component.mount = (component, write) => {
- if (Component.isMounted(component)) {
- throw Error("Can not mount already mounted component");
- }
-
- component[writer] = write;
- Component.write(component);
-
- if (component[Component.mounted]) {
- component[Component.mounted]();
- }
-}
-
-// Unmounts component from a writer.
-Component.unmount = (component) => {
- if (Component.isMounted(component)) {
- component[writer] = null;
- if (component[Component.unmounted]) {
- component[Component.unmounted]();
- }
- } else {
- console.warn("Unmounting component that is not mounted is redundant");
- }
-};
- // Method invoked once after inital write occurs.
-Component.mounted = Symbol("component/mounted");
-// Internal method that unmounts component from the writer.
-Component.unmounted = Symbol("component/unmounted");
-// Function that must return true if component is changed
-Component.isUpdated = Symbol("component/updated?");
-Component.update = Symbol("component/update");
-Component.updated = Symbol("component/updated");
-
-const writeChild = base => (child, index) => Component.write(child, base, index)
-Component.write = (component, base, index) => {
- if (component === null) {
- return component;
- }
-
- if (!(component instanceof Component)) {
- const path = base ? `${base}${component.key || index}/` : `/`;
- return Object.assign({}, component, {
- [Component.path]: path,
- children: component.children && component.children.
- map(writeChild(path)).
- filter(isntNull)
- });
- }
-
- component[isWriting] = true;
-
- try {
-
- const current = component[currentState];
- const pending = component[pendingState] || current;
- const isUpdated = component[Component.isUpdated];
- const isInitial = component[isFirstWrite];
-
- if (isUpdated(current, pending) || isInitial) {
- if (!isInitial && component[Component.update]) {
- component[Component.update](pending, current)
- }
-
- // Note: [Component.update] could have caused more updates so can't use
- // `pending` as `component[pendingState]` may have changed.
- component[currentState] = component[pendingState] || current;
- component[pendingState] = null;
-
- const tree = component[Component.render](component[currentState]);
- component[cache] = Component.write(tree, base, index);
- if (component[writer]) {
- component[writer].call(null, component[cache]);
- }
-
- if (!isInitial && component[Component.updated]) {
- component[Component.updated](current, pending);
- }
- }
-
- component[isFirstWrite] = false;
-
- return component[cache];
- } finally {
- component[isWriting] = false;
- }
-};
-
-Component.prototype = Object.freeze({
- constructor: Component,
-
- [Component.mounted]: null,
- [Component.unmounted]: null,
- [Component.update]: null,
- [Component.updated]: null,
-
- get state() {
- return this[pendingState] || this[currentState];
- },
-
-
- [Component.construct](settings, items) {
- const initial = this[Component.initial];
- const base = initial(settings, items);
- const options = Object.assign(Object.create(null), base.options, settings);
- const children = base.children || items || null;
- const state = Object.assign(Object.create(null), base, {options, children});
- this[currentState] = state;
-
- if (this.setup) {
- this.setup(state);
- }
- },
- [Component.initial](options, children) {
- return Object.create(null);
- },
- [Component.patch](update) {
- this[Component.reset](Object.assign({}, this.state, update));
- },
- [Component.reset](state) {
- this[pendingState] = state;
- if (Component.isMounted(this) && !Component.isWriting(this)) {
- Component.write(this);
- }
- },
-
- [Component.isUpdated](before, after) {
- return before != after
- },
-
- [Component.render](state) {
- throw Error("Component must implement [Component.render] member");
- }
-});
-
-module.exports = Component;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/frame.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* 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": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Frame } = require("./frame/model");
-
-exports.Frame = Frame;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/frame/model.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/* 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": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Class } = require("../../core/heritage");
-const { EventTarget } = require("../../event/target");
-lazyRequire(this, "../../event/core", "emit", "off", "setListeners");
-const { Reactor, foldp, send, merges } = require("../../event/utils");
-const { Disposable } = require("../../core/disposable");
-const { OutputPort } = require("../../output/system");
-lazyRequire(this, "../id", "identify");
-const { pairs, object, each } = require("../../util/sequence");
-lazyRequire(this, "diffpatcher/index", "patch", "diff");
-lazyRequire(this, "../../url", "isLocalURL");
-const { compose } = require("../../lang/functional");
-const { contract } = require("../../util/contract");
-const { id: addonID, data: { url: resolve }} = require("../../self");
-const { Frames } = require("../../input/frame");
-require("./view");
-
-
-const output = new OutputPort({ id: "frame-change" });
-const mailbox = new OutputPort({ id: "frame-mailbox" });
-const input = Frames;
-
-
-const makeID = url =>
- ("frame-" + addonID + "-" + url).
- split("/").join("-").
- split(".").join("-").
- replace(/[^A-Za-z0-9_\-]/g, "");
-
-const validate = contract({
- name: {
- is: ["string", "undefined"],
- ok: x => /^[a-z][a-z0-9-_]+$/i.test(x),
- msg: "The `option.name` must be a valid alphanumeric string (hyphens and " +
- "underscores are allowed) starting with letter."
- },
- url: {
- map: x => x.toString(),
- is: ["string"],
- ok: x => isLocalURL(x),
- msg: "The `options.url` must be a valid local URI."
- }
-});
-
-const Source = function({id, ownerID}) {
- this.id = id;
- this.ownerID = ownerID;
-};
-Source.postMessage = ({id, ownerID}, data, origin) => {
- send(mailbox, object([id, {
- inbox: {
- target: {id: id, ownerID: ownerID},
- timeStamp: Date.now(),
- data: data,
- origin: origin
- }
- }]));
-};
-Source.prototype.postMessage = function(data, origin) {
- Source.postMessage(this, data, origin);
-};
-
-const Message = function({type, data, source, origin, timeStamp}) {
- this.type = type;
- this.data = data;
- this.origin = origin;
- this.timeStamp = timeStamp;
- this.source = new Source(source);
-};
-
-
-const frames = new Map();
-const sources = new Map();
-
-const Frame = Class({
- extends: EventTarget,
- implements: [Disposable, Source],
- initialize: function(params={}) {
- const options = validate(params);
- const id = makeID(options.name || options.url);
-
- if (frames.has(id))
- throw Error("Frame with this id already exists: " + id);
-
- const initial = { id: id, url: resolve(options.url) };
- this.id = id;
-
- setListeners(this, params);
-
- frames.set(this.id, this);
-
- send(output, object([id, initial]));
- },
- get url() {
- const state = reactor.value[this.id];
- return state && state.url;
- },
- destroy: function() {
- send(output, object([this.id, null]));
- frames.delete(this.id);
- off(this);
- },
- // `JSON.stringify` serializes objects based of the return
- // value of this method. For convinienc we provide this method
- // to serialize actual state data.
- toJSON: function() {
- return { id: this.id, url: this.url };
- }
-});
-identify.define(Frame, frame => frame.id);
-
-exports.Frame = Frame;
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- each(([id, update]) => {
- const frame = frames.get(id);
- if (update) {
- if (!past[id])
- emit(frame, "register");
-
- if (update.outbox)
- emit(frame, "message", new Message(present[id].outbox));
-
- each(([ownerID, state]) => {
- const readyState = state ? state.readyState : "detach";
- const type = readyState === "loading" ? "attach" :
- readyState === "interactive" ? "ready" :
- readyState === "complete" ? "load" :
- readyState;
-
- // TODO: Cache `Source` instances somewhere to preserve
- // identity.
- emit(frame, type, {type: type,
- source: new Source({id: id, ownerID: ownerID})});
- }, pairs(update.owners));
- }
- }, pairs(delta));
- }
-});
-reactor.run(input);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/frame/view.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <script>
- // HACK: This is not an ideal way to deliver chrome messages
- // to an inner frame content but seems only way that would
- // make `event.source` this (outer frame) window.
- window.onmessage = function(event) {
- var frame = document.querySelector("iframe");
- var content = frame.contentWindow;
- // If message is posted from chrome it has no `event.source`.
- if (event.source === null)
- content.postMessage(event.data, "*");
- };
- </script>
- </head>
- <body style="overflow: hidden"></body>
-</html>
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/frame/view.js
+++ /dev/null
@@ -1,145 +0,0 @@
-/* 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": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Cu, Ci } = require("chrome");
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { send, Reactor } = require("../../event/utils");
-const { OutputPort } = require("../../output/system");
-lazyRequire(this, "../../util/sequence", "pairs", "keys", "object", "each");
-const { curry, compose } = require("../../lang/functional");
-lazyRequire(this, "../../window/utils", "getFrameElement", "getOuterId", "getByOuterId", "getOwnerBrowserWindow");
-lazyRequire(this, "diffpatcher/index", "patch", "diff");
-lazyRequire(this, "../../base64", "encode");
-const { Frames } = require("../../input/frame");
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
-
-const mailbox = new OutputPort({ id: "frame-mailbox" });
-
-const frameID = frame => frame.id.replace("outer-", "");
-const windowID = compose(getOuterId, getOwnerBrowserWindow);
-
-const getOuterFrame = (windowID, frameID) =>
- getByOuterId(windowID).document.getElementById("outer-" + frameID);
-
-const listener = ({target, source, data, origin, timeStamp}) => {
- // And sent received message to outbox so that frame API model
- // will deal with it.
- if (source && source !== target) {
- const frame = getFrameElement(target);
- const id = frameID(frame);
- send(mailbox, object([id, {
- outbox: {type: "message",
- source: {id: id, ownerID: windowID(frame)},
- data: data,
- origin: origin,
- timeStamp: timeStamp}}]));
- }
-};
-
-// Utility function used to create frame with a given `state` and
-// inject it into given `window`.
-const registerFrame = ({id, url}) => {
- CustomizableUI.createWidget({
- id: id,
- type: "custom",
- removable: true,
- onBuild: document => {
- let view = document.createElementNS(XUL_NS, "toolbaritem");
- view.setAttribute("id", id);
- view.setAttribute("flex", 2);
-
- let outerFrame = document.createElementNS(XUL_NS, "iframe");
- outerFrame.setAttribute("src", OUTER_FRAME_URI);
- outerFrame.setAttribute("id", "outer-" + id);
- outerFrame.setAttribute("data-is-sdk-outer-frame", true);
- outerFrame.setAttribute("type", "content");
- outerFrame.setAttribute("transparent", true);
- outerFrame.setAttribute("flex", 2);
- outerFrame.setAttribute("style", "overflow: hidden;");
- outerFrame.setAttribute("scrolling", "no");
- outerFrame.setAttribute("disablehistory", true);
- outerFrame.setAttribute("seamless", "seamless");
- outerFrame.addEventListener("load", function() {
- let doc = outerFrame.contentDocument;
-
- let innerFrame = doc.createElementNS(HTML_NS, "iframe");
- innerFrame.setAttribute("id", id);
- innerFrame.setAttribute("src", url);
- innerFrame.setAttribute("seamless", "seamless");
- innerFrame.setAttribute("sandbox", "allow-scripts");
- innerFrame.setAttribute("scrolling", "no");
- innerFrame.setAttribute("data-is-sdk-inner-frame", true);
- innerFrame.setAttribute("style", [ "border:none",
- "position:absolute", "width:100%", "top: 0",
- "left: 0", "overflow: hidden"].join(";"));
-
- doc.body.appendChild(innerFrame);
- }, {capture: true, once: true});
-
- view.appendChild(outerFrame);
-
- return view;
- }
- });
-};
-
-const unregisterFrame = CustomizableUI.destroyWidget;
-
-const deliverMessage = curry((frameID, data, windowID) => {
- const frame = getOuterFrame(windowID, frameID);
- const content = frame && frame.contentWindow;
-
- if (content)
- content.postMessage(data, content.location.origin);
-});
-
-const updateFrame = (id, {inbox, owners}, present) => {
- if (inbox) {
- const { data, target:{ownerID}, source } = present[id].inbox;
- if (ownerID)
- deliverMessage(id, data, ownerID);
- else
- each(deliverMessage(id, data), keys(present[id].owners));
- }
-
- each(setupView(id), pairs(owners));
-};
-
-const setupView = curry((frameID, [windowID, state]) => {
- if (state && state.readyState === "loading") {
- const frame = getOuterFrame(windowID, frameID);
- // Setup a message listener on contentWindow.
- frame.contentWindow.addEventListener("message", listener);
- }
-});
-
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- // Apply frame changes
- each(([id, update]) => {
- if (update === null)
- unregisterFrame(id);
- else if (past[id])
- updateFrame(id, update, present);
- else
- registerFrame(update);
- }, pairs(delta));
- },
- onEnd: state => each(unregisterFrame, keys(state))
-});
-reactor.run(Frames);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/id.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* 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': 'experimental'
-};
-
-const method = require('../../method/core');
-lazyRequire(this, '../util/uuid', "uuid");
-
-// NOTE: use lang/functional memoize when it is updated to use WeakMap
-function memoize(f) {
- const memo = new WeakMap();
-
- return function memoizer(o) {
- let key = o;
- if (!memo.has(key))
- memo.set(key, f.apply(this, arguments));
- return memo.get(key);
- };
-}
-
-var identify = method('identify');
-identify.define(Object, memoize(function() { return uuid(); }));
-exports.identify = identify;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar.js
+++ /dev/null
@@ -1,304 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '*'
- }
-};
-
-const { Class } = require('../core/heritage');
-const { merge } = require('../util/object');
-const { Disposable } = require('../core/disposable');
-lazyRequire(this, '../event/core', "off", "emit", "setListeners");
-const { EventTarget } = require('../event/target');
-lazyRequire(this, '../url', "URL");
-lazyRequire(this, '../self', { "id": "addonID" }, "data");
-lazyRequire(this, '../deprecated/window-utils', 'WindowTracker');
-lazyRequire(this, './sidebar/utils', "isShowing");
-lazyRequire(this, '../window/utils', "isBrowser", "getMostRecentBrowserWindow", "windows", "isWindowPrivate");
-const { ns } = require('../core/namespace');
-lazyRequire(this, '../util/array', { "remove": "removeFromArray" });
-lazyRequire(this, './sidebar/actions', "show", "hide", "toggle");
-lazyRequire(this, '../deprecated/sync-worker', "Worker");
-const { contract: sidebarContract } = require('./sidebar/contract');
-lazyRequire(this, './sidebar/view', "create", "dispose", "updateTitle", "updateURL", "isSidebarShowing", "showSidebar", "hideSidebar");
-lazyRequire(this, '../core/promise', "defer");
-lazyRequire(this, './sidebar/namespace', "models", "views", "viewsFor", "modelFor");
-lazyRequire(this, '../url', "isLocalURL");
-const { ensure } = require('../system/unload');
-lazyRequire(this, './id', "identify");
-lazyRequire(this, '../util/uuid', "uuid");
-lazyRequire(this, '../view/core', "viewFor");
-
-const resolveURL = (url) => url ? data.url(url) : url;
-
-const sidebarNS = ns();
-
-const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
-
-const Sidebar = Class({
- implements: [ Disposable ],
- extends: EventTarget,
- setup: function(options) {
- // inital validation for the model information
- let model = sidebarContract(options);
-
- // save the model information
- models.set(this, model);
-
- // generate an id if one was not provided
- model.id = model.id || addonID + '-' + uuid();
-
- // further validation for the title and url
- validateTitleAndURLCombo({}, this.title, this.url);
-
- const self = this;
- const internals = sidebarNS(self);
- const windowNS = internals.windowNS = ns();
-
- // see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148
- ensure(this, 'destroy');
-
- setListeners(this, options);
-
- let bars = [];
- internals.tracker = WindowTracker({
- onTrack: function(window) {
- if (!isBrowser(window))
- return;
-
- let sidebar = window.document.getElementById('sidebar');
- let sidebarBox = window.document.getElementById('sidebar-box');
-
- let bar = create(window, {
- id: self.id,
- title: self.title,
- sidebarurl: self.url
- });
- bars.push(bar);
- windowNS(window).bar = bar;
-
- bar.addEventListener('command', function() {
- if (isSidebarShowing(window, self)) {
- hideSidebar(window, self).catch(() => {});
- return;
- }
-
- showSidebar(window, self);
- });
-
- function onSidebarLoad() {
- // check if the sidebar is ready
- let isReady = sidebar.docShell && sidebar.contentDocument;
- if (!isReady)
- return;
-
- // check if it is a web panel
- let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
- if (!panelBrowser) {
- bar.removeAttribute('checked');
- return;
- }
-
- let sbTitle = window.document.getElementById('sidebar-title');
- function onWebPanelSidebarCreated() {
- if (panelBrowser.contentWindow.location != resolveURL(model.url) ||
- sbTitle.value != model.title) {
- return;
- }
-
- let worker = windowNS(window).worker = Worker({
- window: panelBrowser.contentWindow,
- injectInDocument: true
- });
-
- function onWebPanelSidebarUnload() {
- windowNS(window).onWebPanelSidebarUnload = null;
-
- // uncheck the associated menuitem
- bar.setAttribute('checked', 'false');
-
- emit(self, 'hide', {});
- emit(self, 'detach', worker);
- windowNS(window).worker = null;
- }
- windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload;
- panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true);
-
- // check the associated menuitem
- bar.setAttribute('checked', 'true');
-
- function onWebPanelSidebarReady() {
- panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', onWebPanelSidebarReady);
- windowNS(window).onWebPanelSidebarReady = null;
-
- emit(self, 'ready', worker);
- }
- windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady;
- panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady);
-
- function onWebPanelSidebarLoad() {
- panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true);
- windowNS(window).onWebPanelSidebarLoad = null;
-
- // TODO: decide if returning worker is acceptable..
- //emit(self, 'show', { worker: worker });
- emit(self, 'show', {});
- }
- windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad;
- panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true);
-
- emit(self, 'attach', worker);
- }
- windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated;
- panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true);
- }
- windowNS(window).onSidebarLoad = onSidebarLoad;
- sidebar.addEventListener('load', onSidebarLoad, true); // removed properly
- },
- onUntrack: function(window) {
- if (!isBrowser(window))
- return;
-
- // hide the sidebar if it is showing
- hideSidebar(window, self).catch(() => {});
-
- // kill the menu item
- let { bar } = windowNS(window);
- if (bar) {
- removeFromArray(viewsFor(self), bar);
- dispose(bar);
- }
-
- // kill listeners
- let sidebar = window.document.getElementById('sidebar');
-
- if (windowNS(window).onSidebarLoad) {
- sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true)
- windowNS(window).onSidebarLoad = null;
- }
-
- let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
- if (windowNS(window).onWebPanelSidebarCreated) {
- panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true);
- windowNS(window).onWebPanelSidebarCreated = null;
- }
-
- if (windowNS(window).onWebPanelSidebarReady) {
- panelBrowser && panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', windowNS(window).onWebPanelSidebarReady);
- windowNS(window).onWebPanelSidebarReady = null;
- }
-
- if (windowNS(window).onWebPanelSidebarLoad) {
- panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true);
- windowNS(window).onWebPanelSidebarLoad = null;
- }
-
- if (windowNS(window).onWebPanelSidebarUnload) {
- panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true);
- windowNS(window).onWebPanelSidebarUnload();
- }
- }
- });
-
- views.set(this, bars);
- },
- get id() {
- return (modelFor(this) || {}).id;
- },
- get title() {
- return (modelFor(this) || {}).title;
- },
- set title(v) {
- // destroyed?
- if (!modelFor(this))
- return;
- // validation
- if (typeof v != 'string')
- throw Error('title must be a string');
- validateTitleAndURLCombo(this, v, this.url);
- // do update
- updateTitle(this, v);
- return modelFor(this).title = v;
- },
- get url() {
- return (modelFor(this) || {}).url;
- },
- set url(v) {
- // destroyed?
- if (!modelFor(this))
- return;
-
- // validation
- if (!isLocalURL(v))
- throw Error('the url must be a valid local url');
-
- validateTitleAndURLCombo(this, this.title, v);
-
- // do update
- updateURL(this, v);
- modelFor(this).url = v;
- },
- show: function(window) {
- return showSidebar(viewFor(window), this);
- },
- hide: function(window) {
- return hideSidebar(viewFor(window), this);
- },
- dispose: function() {
- const internals = sidebarNS(this);
-
- off(this);
-
- // stop tracking windows
- if (internals.tracker) {
- internals.tracker.unload();
- }
-
- internals.tracker = null;
- internals.windowNS = null;
-
- views.delete(this);
- models.delete(this);
- }
-});
-exports.Sidebar = Sidebar;
-
-function validateTitleAndURLCombo(sidebar, title, url) {
- url = resolveURL(url);
-
- if (sidebar.title == title && sidebar.url == url) {
- return false;
- }
-
- for (let window of windows(null, { includePrivate: true })) {
- let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]');
- if (sidebar) {
- throw Error('The provided title and url combination is invalid (already used).');
- }
- }
-
- return false;
-}
-
-isShowing.define(Sidebar, isSidebarShowing.bind(null, null));
-show.define(Sidebar, showSidebar.bind(null, null));
-hide.define(Sidebar, hideSidebar.bind(null, null));
-
-identify.define(Sidebar, function(sidebar) {
- return sidebar.id;
-});
-
-function toggleSidebar(window, sidebar) {
- // TODO: make sure this is not private
- window = window || getMostRecentBrowserWindow();
- if (isSidebarShowing(window, sidebar)) {
- return hideSidebar(window, sidebar);
- }
- return showSidebar(window, sidebar);
-}
-toggle.define(Sidebar, toggleSidebar.bind(null, null));
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar/actions.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* 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';
-
-const method = require('../../../method/core');
-
-exports.show = method('show');
-exports.hide = method('hide');
-exports.toggle = method('toggle');
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar/contract.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* 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';
-
-const { contract } = require('../../util/contract');
-const { isValidURI, URL, isLocalURL } = require('../../url');
-const { isNil, isObject, isString } = require('../../lang/type');
-
-exports.contract = contract({
- id: {
- is: [ 'string', 'undefined' ],
- ok: v => /^[a-z0-9-_]+$/i.test(v),
- msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
- 'underscores are allowed).'
- },
- title: {
- is: [ 'string' ],
- ok: v => v.length
- },
- url: {
- is: [ 'string' ],
- ok: v => isLocalURL(v),
- map: v => v.toString(),
- msg: 'The option "url" must be a valid local URI.'
- }
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* 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';
-
-const models = exports.models = new WeakMap();
-const views = exports.views = new WeakMap();
-exports.buttons = new WeakMap();
-
-exports.viewsFor = function viewsFor(sidebar) {
- return views.get(sidebar);
-};
-exports.modelFor = function modelFor(sidebar) {
- return models.get(sidebar);
-};
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar/utils.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/* 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';
-
-const method = require('../../../method/core');
-
-exports.isShowing = method('isShowing');
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar/view.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/* 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',
- 'engines': {
- 'Firefox': '*'
- }
-};
-
-lazyRequire(this, './namespace', "models", "buttons", "views", "viewsFor", "modelFor");
-lazyRequire(this, '../../window/utils', "isBrowser", "getMostRecentBrowserWindow", "windows", "isWindowPrivate");
-lazyRequire(this, '../state', "setStateFor");
-lazyRequire(this, '../../core/promise', "defer");
-lazyRequire(this, '../../self', "isPrivateBrowsingSupported", "data");
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
-
-const resolveURL = (url) => url ? data.url(url) : url;
-
-function create(window, details) {
- let id = makeID(details.id);
- let { document } = window;
-
- if (document.getElementById(id))
- throw new Error('The ID "' + details.id + '" seems already used.');
-
- let menuitem = document.createElementNS(XUL_NS, 'menuitem');
- menuitem.setAttribute('id', id);
- menuitem.setAttribute('label', details.title);
- menuitem.setAttribute('sidebarurl', resolveURL(details.sidebarurl));
- menuitem.setAttribute('checked', 'false');
- menuitem.setAttribute('type', 'checkbox');
- menuitem.setAttribute('group', 'sidebar');
- menuitem.setAttribute('autoCheck', 'false');
-
- document.getElementById('viewSidebarMenu').appendChild(menuitem);
-
- return menuitem;
-}
-exports.create = create;
-
-function dispose(menuitem) {
- menuitem.remove();
-}
-exports.dispose = dispose;
-
-function updateTitle(sidebar, title) {
- let button = buttons.get(sidebar);
-
- for (let window of windows(null, { includePrivate: true })) {
- let { document } = window;
-
- // update the button
- if (button) {
- setStateFor(button, window, { label: title });
- }
-
- // update the menuitem
- let mi = document.getElementById(makeID(sidebar.id));
- if (mi) {
- mi.setAttribute('label', title)
- }
-
- // update sidebar, if showing
- if (isSidebarShowing(window, sidebar)) {
- document.getElementById('sidebar-title').setAttribute('value', title);
- }
- }
-}
-exports.updateTitle = updateTitle;
-
-function updateURL(sidebar, url) {
- let eleID = makeID(sidebar.id);
-
- url = resolveURL(url);
-
- for (let window of windows(null, { includePrivate: true })) {
- // update the menuitem
- let mi = window.document.getElementById(eleID);
- if (mi) {
- mi.setAttribute('sidebarurl', url)
- }
-
- // update sidebar, if showing
- if (isSidebarShowing(window, sidebar)) {
- showSidebar(window, sidebar, url);
- }
- }
-}
-exports.updateURL = updateURL;
-
-function isSidebarShowing(window, sidebar) {
- let win = window || getMostRecentBrowserWindow();
-
- // make sure there is a window
- if (!win) {
- return false;
- }
-
- // make sure there is a sidebar for the window
- let sb = win.document.getElementById('sidebar');
- let sidebarTitle = win.document.getElementById('sidebar-title');
- if (!(sb && sidebarTitle)) {
- return false;
- }
-
- // checks if the sidebar box is hidden
- let sbb = win.document.getElementById('sidebar-box');
- if (!sbb || sbb.hidden) {
- return false;
- }
-
- if (sidebarTitle.value == modelFor(sidebar).title) {
- let url = resolveURL(modelFor(sidebar).url);
-
- // checks if the sidebar is loading
- if (win.gWebPanelURI == url) {
- return true;
- }
-
- // checks if the sidebar loaded already
- let ele = sb.contentDocument && sb.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
- if (!ele) {
- return false;
- }
-
- if (ele.getAttribute('cachedurl') == url) {
- return true;
- }
-
- if (ele && ele.contentWindow && ele.contentWindow.location == url) {
- return true;
- }
- }
-
- // default
- return false;
-}
-exports.isSidebarShowing = isSidebarShowing;
-
-function showSidebar(window, sidebar, newURL) {
- window = window || getMostRecentBrowserWindow();
-
- let { promise, resolve, reject } = defer();
- let model = modelFor(sidebar);
-
- if (!newURL && isSidebarShowing(window, sidebar)) {
- resolve({});
- }
- else if (!isPrivateBrowsingSupported && isWindowPrivate(window)) {
- reject(Error('You cannot show a sidebar on private windows'));
- }
- else {
- sidebar.once('show', resolve);
-
- let menuitem = window.document.getElementById(makeID(model.id));
- menuitem.setAttribute('checked', true);
-
- window.openWebPanel(model.title, resolveURL(newURL || model.url));
- }
-
- return promise;
-}
-exports.showSidebar = showSidebar;
-
-
-function hideSidebar(window, sidebar) {
- window = window || getMostRecentBrowserWindow();
-
- let { promise, resolve, reject } = defer();
-
- if (!isSidebarShowing(window, sidebar)) {
- reject(Error('The sidebar is already hidden'));
- }
- else {
- sidebar.once('hide', resolve);
-
- // Below was taken from http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4775
- // the code for window.todggleSideBar()..
- let { document } = window;
- let sidebarEle = document.getElementById('sidebar');
- let sidebarTitle = document.getElementById('sidebar-title');
- let sidebarBox = document.getElementById('sidebar-box');
- let sidebarSplitter = document.getElementById('sidebar-splitter');
- let commandID = sidebarBox.getAttribute('sidebarcommand');
- let sidebarBroadcaster = document.getElementById(commandID);
-
- sidebarBox.hidden = true;
- sidebarSplitter.hidden = true;
-
- sidebarEle.setAttribute('src', 'about:blank');
- //sidebarEle.docShell.createAboutBlankContentViewer(null);
-
- sidebarBroadcaster.removeAttribute('checked');
- sidebarBox.setAttribute('sidebarcommand', '');
- sidebarTitle.value = '';
- sidebarBox.hidden = true;
- sidebarSplitter.hidden = true;
-
- // TODO: perhaps this isn't necessary if the window is not most recent?
- window.gBrowser.selectedBrowser.focus();
- }
-
- return promise;
-}
-exports.hideSidebar = hideSidebar;
-
-function makeID(id) {
- return 'jetpack-sidebar-' + id;
-}
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/state.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/* 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';
-
-// The Button module currently supports only Firefox.
-// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '*',
- 'SeaMonkey': '*',
- 'Thunderbird': '*'
- }
-};
-
-const { Ci } = require('chrome');
-
-const events = require('../event/utils');
-const { events: browserEvents } = require('../browser/events');
-const { events: tabEvents } = require('../tab/events');
-const { events: stateEvents } = require('./state/events');
-
-lazyRequire(this, '../window/utils', "windows", "isInteractive", "getFocusedBrowser");
-lazyRequire(this, '../tabs/utils', "getActiveTab", "getOwnerWindow");
-
-lazyRequire(this, '../private-browsing/utils', "ignoreWindow");
-
-const { freeze } = Object;
-const { merge } = require('../util/object');
-lazyRequire(this, '../event/core', "on", "off", "emit");
-
-lazyRequire(this, '../lang/weak-set', "add", "remove", "has", "clear", "iterator");
-lazyRequire(this, '../lang/type', "isNil");
-
-lazyRequire(this, '../view/core', "viewFor");
-
-const components = new WeakMap();
-
-const ERR_UNREGISTERED = 'The state cannot be set or get. ' +
- 'The object may be not be registered, or may already have been unloaded.';
-
-const ERR_INVALID_TARGET = 'The state cannot be set or get for this target.' +
- 'Only window, tab and registered component are valid targets.';
-
-const isWindow = thing => thing instanceof Ci.nsIDOMWindow;
-const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab';
-const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing));
-const isEnumerable = window => !ignoreWindow(window);
-const browsers = _ =>
- windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
-const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());
-
-function getStateFor(component, target) {
- if (!isRegistered(component))
- throw new Error(ERR_UNREGISTERED);
-
- if (!components.has(component))
- return null;
-
- let states = components.get(component);
-
- if (target) {
- if (isTab(target) || isWindow(target) || target === component)
- return states.get(target) || null;
- else
- throw new Error(ERR_INVALID_TARGET);
- }
-
- return null;
-}
-exports.getStateFor = getStateFor;
-
-function getDerivedStateFor(component, target) {
- if (!isRegistered(component))
- throw new Error(ERR_UNREGISTERED);
-
- if (!components.has(component))
- return null;
-
- let states = components.get(component);
-
- let componentState = states.get(component);
- let windowState = null;
- let tabState = null;
-
- if (target) {
- // has a target
- if (isTab(target)) {
- windowState = states.get(getOwnerWindow(target), null);
-
- if (states.has(target)) {
- // we have a tab state
- tabState = states.get(target);
- }
- }
- else if (isWindow(target) && states.has(target)) {
- // we have a window state
- windowState = states.get(target);
- }
- }
-
- return freeze(merge({}, componentState, windowState, tabState));
-}
-exports.getDerivedStateFor = getDerivedStateFor;
-
-function setStateFor(component, target, state) {
- if (!isRegistered(component))
- throw new Error(ERR_UNREGISTERED);
-
- let isComponentState = target === component;
- let targetWindows = isWindow(target) ? [target] :
- isActiveTab(target) ? [getOwnerWindow(target)] :
- isComponentState ? browsers() :
- isTab(target) ? [] :
- null;
-
- if (!targetWindows)
- throw new Error(ERR_INVALID_TARGET);
-
- // initialize the state's map
- if (!components.has(component))
- components.set(component, new WeakMap());
-
- let states = components.get(component);
-
- if (state === null && !isComponentState) // component state can't be deleted
- states.delete(target);
- else {
- let base = isComponentState ? states.get(target) : null;
- states.set(target, freeze(merge({}, base, state)));
- }
-
- render(component, targetWindows);
-}
-exports.setStateFor = setStateFor;
-
-function render(component, targetWindows) {
- targetWindows = targetWindows ? [].concat(targetWindows) : browsers();
-
- for (let window of targetWindows.filter(isEnumerable)) {
- let tabState = getDerivedStateFor(component, getActiveTab(window));
-
- emit(stateEvents, 'data', {
- type: 'render',
- target: component,
- window: window,
- state: tabState
- });
-
- }
-}
-exports.render = render;
-
-function properties(contract) {
- let { rules } = contract;
- let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
- descriptor[name] = {
- get: function() { return getDerivedStateFor(this)[name] },
- set: function(value) {
- let changed = {};
- changed[name] = value;
-
- setStateFor(this, this, contract(changed));
- }
- }
- return descriptor;
- }, {});
-
- return Object.create(Object.prototype, descriptor);
-}
-exports.properties = properties;
-
-function state(contract) {
- return {
- state: function state(target, state) {
- let nativeTarget = target === 'window' ? getFocusedBrowser()
- : target === 'tab' ? getMostRecentTab()
- : target === this ? null
- : viewFor(target);
-
- if (!nativeTarget && target !== this && !isNil(target))
- throw new Error(ERR_INVALID_TARGET);
-
- target = nativeTarget || target;
-
- // jquery style
- return arguments.length < 2
- ? getDerivedStateFor(this, target)
- : setStateFor(this, target, contract(state))
- }
- }
-}
-exports.state = state;
-
-const register = (component, state) => {
- add(components, component);
- setStateFor(component, component, state);
-}
-exports.register = register;
-
-const unregister = component => {
- remove(components, component);
-}
-exports.unregister = unregister;
-
-const isRegistered = component => has(components, component);
-exports.isRegistered = isRegistered;
-
-var tabSelect = events.filter(tabEvents, e => e.type === 'TabSelect');
-var tabClose = events.filter(tabEvents, e => e.type === 'TabClose');
-var windowOpen = events.filter(browserEvents, e => e.type === 'load');
-var windowClose = events.filter(browserEvents, e => e.type === 'close');
-
-var close = events.merge([tabClose, windowClose]);
-var activate = events.merge([windowOpen, tabSelect]);
-
-on(activate, 'data', ({target}) => {
- let [window, tab] = isWindow(target)
- ? [target, getActiveTab(target)]
- : [getOwnerWindow(target), target];
-
- if (ignoreWindow(window)) return;
-
- for (let component of iterator(components)) {
- emit(stateEvents, 'data', {
- type: 'render',
- target: component,
- window: window,
- state: getDerivedStateFor(component, tab)
- });
- }
-});
-
-on(close, 'data', function({target}) {
- for (let component of iterator(components)) {
- components.get(component).delete(target);
- }
-});
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/state/events.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* 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': 'experimental',
- 'engines': {
- 'Firefox': '*',
- 'SeaMonkey': '*',
- 'Thunderbird': '*'
- }
-};
-
-var channel = {};
-
-exports.events = channel;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/toolbar.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* 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": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Toolbar } = require("./toolbar/model");
-require("./toolbar/view");
-
-exports.Toolbar = Toolbar;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/toolbar/model.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/* 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": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Class } = require("../../core/heritage");
-const { EventTarget } = require("../../event/target");
-const { off, setListeners, emit } = require("../../event/core");
-const { Reactor, foldp, merges, send } = require("../../event/utils");
-const { Disposable } = require("../../core/disposable");
-const { InputPort } = require("../../input/system");
-const { OutputPort } = require("../../output/system");
-const { identify } = require("../id");
-const { pairs, object, map, each } = require("../../util/sequence");
-const { patch, diff } = require("diffpatcher/index");
-const { contract } = require("../../util/contract");
-const { id: addonID } = require("../../self");
-
-// Input state is accumulated from the input received form the toolbar
-// view code & local output. Merging local output reflects local state
-// changes without complete roundloop.
-const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" }));
-const output = new OutputPort({ id: "toolbar-change" });
-
-// Takes toolbar title and normalizes is to an
-// identifier, also prefixes with add-on id.
-const titleToId = title =>
- ("toolbar-" + addonID + "-" + title).
- toLowerCase().
- replace(/\s/g, "-").
- replace(/[^A-Za-z0-9_\-]/g, "");
-
-const validate = contract({
- title: {
- is: ["string"],
- ok: x => x.length > 0,
- msg: "The `option.title` string must be provided"
- },
- items: {
- is:["undefined", "object", "array"],
- msg: "The `options.items` must be iterable sequence of items"
- },
- hidden: {
- is: ["boolean", "undefined"],
- msg: "The `options.hidden` must be boolean"
- }
-});
-
-// Toolbars is a mapping between `toolbar.id` & `toolbar` instances,
-// which is used to find intstance for dispatching events.
-var toolbars = new Map();
-
-const Toolbar = Class({
- extends: EventTarget,
- implements: [Disposable],
- initialize: function(params={}) {
- const options = validate(params);
- const id = titleToId(options.title);
-
- if (toolbars.has(id))
- throw Error("Toolbar with this id already exists: " + id);
-
- // Set of the items in the toolbar isn't mutable, as a matter of fact
- // it just defines desired set of items, actual set is under users
- // control. Conver test to an array and freeze to make sure users won't
- // try mess with it.
- const items = Object.freeze(options.items ? [...options.items] : []);
-
- const initial = {
- id: id,
- title: options.title,
- // By default toolbars are visible when add-on is installed, unless
- // add-on authors decides it should be hidden. From that point on
- // user is in control.
- collapsed: !!options.hidden,
- // In terms of state only identifiers of items matter.
- items: items.map(identify)
- };
-
- this.id = id;
- this.items = items;
-
- toolbars.set(id, this);
- setListeners(this, params);
-
- // Send initial state to the host so it can reflect it
- // into a user interface.
- send(output, object([id, initial]));
- },
-
- get title() {
- const state = reactor.value[this.id];
- return state && state.title;
- },
- get hidden() {
- const state = reactor.value[this.id];
- return state && state.collapsed;
- },
-
- destroy: function() {
- send(output, object([this.id, null]));
- },
- // `JSON.stringify` serializes objects based of the return
- // value of this method. For convinienc we provide this method
- // to serialize actual state data. Note: items will also be
- // serialized so they should probably implement `toJSON`.
- toJSON: function() {
- return {
- id: this.id,
- title: this.title,
- hidden: this.hidden,
- items: this.items
- };
- }
-});
-exports.Toolbar = Toolbar;
-identify.define(Toolbar, toolbar => toolbar.id);
-
-const dispose = toolbar => {
- toolbars.delete(toolbar.id);
- emit(toolbar, "detach");
- off(toolbar);
-};
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- each(([id, update]) => {
- const toolbar = toolbars.get(id);
-
- // Remove
- if (!update)
- dispose(toolbar);
- // Add
- else if (!past[id])
- emit(toolbar, "attach");
- // Update
- else
- emit(toolbar, update.collapsed ? "hide" : "show", toolbar);
- }, pairs(delta));
- }
-});
-reactor.run(input);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/ui/toolbar/view.js
+++ /dev/null
@@ -1,248 +0,0 @@
-/* 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": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Cu } = require("chrome");
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils");
-const { InputPort } = require("../../input/system");
-const { OutputPort } = require("../../output/system");
-const { Interactive } = require("../../input/browser");
-const { CustomizationInput } = require("../../input/customizable-ui");
-const { pairs, map, isEmpty, object,
- each, keys, values } = require("../../util/sequence");
-const { curry, flip } = require("../../lang/functional");
-lazyRequire(this, "diffpatcher/index", "patch", "diff");
-const prefs = require("../../preferences/service");
-lazyRequire(this, "../../window/utils", "getByOuterId");
-lazyRequire(this, '../../private-browsing/utils', "ignoreWindow");
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const PREF_ROOT = "extensions.sdk-toolbar-collapsed.";
-
-
-// There are two output ports one for publishing changes that occured
-// and the other for change requests. Later is synchronous and is only
-// consumed here. Note: it needs to be synchronous to avoid race conditions
-// when `collapsed` attribute changes are caused by user interaction and
-// toolbar is destroyed between the ticks.
-const output = new OutputPort({ id: "toolbar-changed" });
-const syncoutput = new OutputPort({ id: "toolbar-change", sync: true });
-
-// Merge disptached changes and recevied changes from models to keep state up to
-// date.
-const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }),
- new InputPort({ id: "toolbar-change" })]));
-const State = lift((toolbars, windows, customizable) =>
- ({windows: windows, toolbars: toolbars, customizable: customizable}),
- Toolbars, Interactive, new CustomizationInput());
-
-// Shared event handler that makes `event.target.parent` collapsed.
-// Used as toolbar's close buttons click handler.
-const collapseToolbar = event => {
- const toolbar = event.target.parentNode;
- toolbar.collapsed = true;
-};
-
-const parseAttribute = x =>
- x === "true" ? true :
- x === "false" ? false :
- x === "" ? null :
- x;
-
-// Shared mutation observer that is used to observe `toolbar` node's
-// attribute mutations. Mutations are aggregated in the `delta` hash
-// and send to `ToolbarStateChanged` channel to let model know state
-// has changed.
-const attributesChanged = mutations => {
- const delta = mutations.reduce((changes, {attributeName, target}) => {
- const id = target.id;
- const field = attributeName === "toolbarname" ? "title" : attributeName;
- let change = changes[id] || (changes[id] = {});
- change[field] = parseAttribute(target.getAttribute(attributeName));
- return changes;
- }, {});
-
- // Calculate what are the updates from the current state and if there are
- // any send them.
- const updates = diff(reactor.value, patch(reactor.value, delta));
-
- if (!isEmpty(pairs(updates))) {
- // TODO: Consider sending sync to make sure that there won't be a new
- // update doing a delete in the meantime.
- send(syncoutput, updates);
- }
-};
-
-
-// Utility function creates `toolbar` with a "close" button and returns
-// it back. In addition it set's up a listener and observer to communicate
-// state changes.
-const addView = curry((options, {document, window}) => {
- if (ignoreWindow(window))
- return;
-
- let view = document.createElementNS(XUL_NS, "toolbar");
- view.setAttribute("id", options.id);
- view.setAttribute("collapsed", options.collapsed);
- view.setAttribute("toolbarname", options.title);
- view.setAttribute("pack", "end");
- view.setAttribute("customizable", "false");
- view.setAttribute("style", "padding: 2px 0; max-height: 40px;");
- view.setAttribute("mode", "icons");
- view.setAttribute("iconsize", "small");
- view.setAttribute("context", "toolbar-context-menu");
- view.setAttribute("class", "chromeclass-toolbar");
-
- let label = document.createElementNS(XUL_NS, "label");
- label.setAttribute("value", options.title);
- label.setAttribute("collapsed", "true");
- view.appendChild(label);
-
- let closeButton = document.createElementNS(XUL_NS, "toolbarbutton");
- closeButton.setAttribute("id", "close-" + options.id);
- closeButton.setAttribute("class", "close-icon");
- closeButton.setAttribute("customizable", false);
- closeButton.addEventListener("command", collapseToolbar);
-
- view.appendChild(closeButton);
-
- // In order to have a close button not costumizable, aligned on the right,
- // leaving the customizable capabilities of Australis, we need to create
- // a toolbar inside a toolbar.
- // This is should be a temporary hack, we should have a proper XBL for toolbar
- // instead. See:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=982005
- let toolbar = document.createElementNS(XUL_NS, "toolbar");
- toolbar.setAttribute("id", "inner-" + options.id);
- toolbar.setAttribute("defaultset", options.items.join(","));
- toolbar.setAttribute("customizable", "true");
- toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden; border: 0;");
- toolbar.setAttribute("mode", "icons");
- toolbar.setAttribute("iconsize", "small");
- toolbar.setAttribute("context", "toolbar-context-menu");
- toolbar.setAttribute("flex", "1");
-
- view.insertBefore(toolbar, closeButton);
-
- const observer = new document.defaultView.MutationObserver(attributesChanged);
- observer.observe(view, { attributes: true,
- attributeFilter: ["collapsed", "toolbarname"] });
-
- const toolbox = document.getElementById("navigator-toolbox");
- toolbox.appendChild(view);
-});
-const viewAdd = curry(flip(addView));
-
-const removeView = curry((id, {document}) => {
- const view = document.getElementById(id);
- if (view) view.remove();
-});
-
-const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => {
- const view = document.getElementById(id);
-
- if (!view)
- return;
-
- if (title)
- view.setAttribute("toolbarname", title);
-
- if (collapsed !== void(0))
- view.setAttribute("collapsed", Boolean(collapsed));
-
- if (isCustomizing !== void(0)) {
- view.querySelector("label").collapsed = !isCustomizing;
- view.querySelector("toolbar").style.visibility = isCustomizing
- ? "hidden" : "visible";
- }
-});
-
-const viewUpdate = curry(flip(updateView));
-
-// Utility function used to register toolbar into CustomizableUI.
-const registerToolbar = state => {
- // If it's first additon register toolbar as customizableUI component.
- CustomizableUI.registerArea("inner-" + state.id, {
- type: CustomizableUI.TYPE_TOOLBAR,
- legacy: true,
- defaultPlacements: [...state.items]
- });
-};
-// Utility function used to unregister toolbar from the CustomizableUI.
-const unregisterToolbar = CustomizableUI.unregisterArea;
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- each(([id, update]) => {
- // If update is `null` toolbar is removed, in such case
- // we unregister toolbar and remove it from each window
- // it was added to.
- if (update === null) {
- unregisterToolbar("inner-" + id);
- each(removeView(id), values(past.windows));
-
- send(output, object([id, null]));
- }
- else if (past.toolbars[id]) {
- // If `collapsed` state for toolbar was updated, persist
- // it for a future sessions.
- if (update.collapsed !== void(0))
- prefs.set(PREF_ROOT + id, update.collapsed);
-
- // Reflect update in each window it was added to.
- each(updateView(id, update), values(past.windows));
-
- send(output, object([id, update]));
- }
- // Hack: Mutation observers are invoked async, which means that if
- // client does `hide(toolbar)` & then `toolbar.destroy()` by the
- // time we'll get update for `collapsed` toolbar will be removed.
- // For now we check if `update.id` is present which will be undefined
- // in such cases.
- else if (update.id) {
- // If it is a new toolbar we create initial state by overriding
- // `collapsed` filed with value persisted in previous sessions.
- const state = patch(update, {
- collapsed: prefs.get(PREF_ROOT + id, update.collapsed),
- });
-
- // Register toolbar and add it each window known in the past
- // (note that new windows if any will be handled in loop below).
- registerToolbar(state);
- each(addView(state), values(past.windows));
-
- send(output, object([state.id, state]));
- }
- }, pairs(delta.toolbars));
-
- // Add views to every window that was added.
- each(window => {
- if (window)
- each(viewAdd(window), values(past.toolbars));
- }, values(delta.windows));
-
- each(([id, isCustomizing]) => {
- each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}),
- keys(present.toolbars));
-
- }, pairs(delta.customizable))
- },
- onEnd: state => {
- each(id => {
- unregisterToolbar("inner-" + id);
- each(removeView(id), values(state.windows));
- }, keys(state.toolbars));
- }
-});
-reactor.run(State);