diff options
Diffstat (limited to 'addon-sdk/source/lib/sdk/context-menu')
-rw-r--r-- | addon-sdk/source/lib/sdk/context-menu/context.js | 147 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/context-menu/core.js | 384 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/context-menu/readers.js | 112 |
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; |