summaryrefslogtreecommitdiffstats
path: root/addon-sdk/source/lib/sdk/context-menu
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-02-09 06:46:43 -0500
committerMatt A. Tobin <email@mattatobin.com>2018-02-09 06:46:43 -0500
commitac46df8daea09899ce30dc8fd70986e258c746bf (patch)
tree2750d3125fc253fd5b0671e4bd268eff1fd97296 /addon-sdk/source/lib/sdk/context-menu
parent8cecf8d5208f3945b35f879bba3015bb1a11bec6 (diff)
downloadUXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.gz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.lz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.xz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.zip
Move Add-on SDK source to toolkit/jetpack
Diffstat (limited to 'addon-sdk/source/lib/sdk/context-menu')
-rw-r--r--addon-sdk/source/lib/sdk/context-menu/context.js147
-rw-r--r--addon-sdk/source/lib/sdk/context-menu/core.js384
-rw-r--r--addon-sdk/source/lib/sdk/context-menu/readers.js112
3 files changed, 0 insertions, 643 deletions
diff --git a/addon-sdk/source/lib/sdk/context-menu/context.js b/addon-sdk/source/lib/sdk/context-menu/context.js
deleted file mode 100644
index fc5aea500..000000000
--- a/addon-sdk/source/lib/sdk/context-menu/context.js
+++ /dev/null
@@ -1,147 +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 { MatchPattern } = require("../util/match-pattern");
-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;
diff --git a/addon-sdk/source/lib/sdk/context-menu/core.js b/addon-sdk/source/lib/sdk/context-menu/core.js
deleted file mode 100644
index c64cddfe8..000000000
--- 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, false);
- },
- 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);
diff --git a/addon-sdk/source/lib/sdk/context-menu/readers.js b/addon-sdk/source/lib/sdk/context-menu/readers.js
deleted file mode 100644
index 5078f8f29..000000000
--- 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;