Bug 1208257 - [webext] browser_action.json (r=kmag)

This commit is contained in:
Bill McCloskey 2015-11-22 08:59:01 -08:00
Родитель 4bcec16ec8
Коммит a3d3a41850
9 изменённых файлов: 408 добавлений и 42 удалений

Просмотреть файл

@ -200,7 +200,7 @@ extensions.on("shutdown", (type, extension) => {
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerAPI((extension, context) => {
extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
return {
browserAction: {
onClicked: new EventManager(context, "browserAction.onClicked", fire => {
@ -215,45 +215,45 @@ extensions.registerAPI((extension, context) => {
}).api(),
enable: function(tabId) {
let tab = tabId ? TabManager.getTab(tabId) : null;
let tab = tabId !== null ? TabManager.getTab(tabId) : null;
browserActionOf(extension).setProperty(tab, "enabled", true);
},
disable: function(tabId) {
let tab = tabId ? TabManager.getTab(tabId) : null;
let tab = tabId !== null ? TabManager.getTab(tabId) : null;
browserActionOf(extension).setProperty(tab, "enabled", false);
},
setTitle: function(details) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "title", details.title);
},
getTitle: function(details, callback) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let title = browserActionOf(extension).getProperty(tab, "title");
runSafe(context, callback, title);
},
setIcon: function(details, callback) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let icon = IconDetails.normalize(details, extension, context);
browserActionOf(extension).setProperty(tab, "icon", icon);
},
setBadgeText: function(details) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "badgeText", details.text);
},
getBadgeText: function(details, callback) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let text = browserActionOf(extension).getProperty(tab, "badgeText");
runSafe(context, callback, text);
},
setPopup: function(details) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
// Note: Chrome resolves arguments to setIcon relative to the calling
// context, but resolves arguments to setPopup relative to the extension
// root.
@ -264,18 +264,18 @@ extensions.registerAPI((extension, context) => {
},
getPopup: function(details, callback) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let popup = browserActionOf(extension).getProperty(tab, "popup");
runSafe(context, callback, popup);
},
setBadgeBackgroundColor: function(details) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "badgeBackgroundColor", details.color);
},
getBadgeBackgroundColor: function(details, callback) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let color = browserActionOf(extension).getProperty(tab, "badgeBackgroundColor");
runSafe(context, callback, color);
},

Просмотреть файл

@ -12,6 +12,7 @@ const INTEGER = /^[1-9]\d*$/;
var {
EventManager,
instanceOf,
} = ExtensionUtils;
// This file provides some useful code for the |tabs| and |windows|
@ -38,7 +39,13 @@ global.IconDetails = {
if (details.imageData) {
let imageData = details.imageData;
if (imageData instanceof Cu.getGlobalForObject(imageData).ImageData) {
// The global might actually be from Schema.jsm, which
// normalizes most of our arguments. In that case it won't have
// an ImageData property. But Schema.jsm doesn't normalize
// actual ImageData objects, so they will come from a global
// with the right property.
let global = Cu.getGlobalForObject(imageData);
if (instanceOf(imageData, "ImageData")) {
imageData = {"19": imageData};
}
@ -46,7 +53,6 @@ global.IconDetails = {
if (!INTEGER.test(size)) {
throw new Error(`Invalid icon size ${size}, must be an integer`);
}
result[size] = this.convertImageDataToPNG(imageData[size], context);
}
}

Просмотреть файл

@ -67,7 +67,6 @@
{
"id": "CreateDetails",
"description": "Object passed to the create() function.",
"inline_doc": true,
"type": "object",
"properties": {
"parentId": {

Просмотреть файл

@ -0,0 +1,344 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"namespace": "browserAction",
"description": "Use browser actions to put icons in the main browser toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup.",
"types": [
{
"id": "ColorArray",
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 255
},
"minItems": 4,
"maxItems": 4
},
{
"id": "ImageDataType",
"type": "object",
"isInstanceOf": "ImageData",
"additionalProperties": { "type": "any" },
"description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
}
],
"functions": [
{
"name": "setTitle",
"type": "function",
"description": "Sets the title of the browser action. This shows up in the tooltip.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The string the browser action should display when moused over."
},
"tabId": {
"type": "integer",
"optional": true,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
]
},
{
"name": "getTitle",
"type": "function",
"description": "Gets the title of the browser action.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"description": "Specify the tab to get the title from. If no tab is specified, the non-tab-specific title is returned."
}
}
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "result",
"type": "string"
}
]
}
]
},
{
"name": "setIcon",
"type": "function",
"description": "Sets the icon for the browser action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"imageData": {
"choices": [
{ "$ref": "ImageDataType" },
{
"type": "object",
"additionalProperties": {"$ref": "ImageDataType"}
}
],
"optional": true,
"description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
},
"path": {
"choices": [
{ "type": "string" },
{
"type": "object",
"additionalProperties": {"type": "string"}
}
],
"optional": true,
"description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
},
"tabId": {
"type": "integer",
"optional": true,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "setPopup",
"type": "function",
"description": "Sets the html document to be opened as a popup when the user clicks on the browser action's icon.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
},
"popup": {
"type": "string",
"description": "The html file to show in a popup. If set to the empty string (''), no popup is shown."
}
}
}
]
},
{
"name": "getPopup",
"type": "function",
"description": "Gets the html document set as the popup for this browser action.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"description": "Specify the tab to get the popup from. If no tab is specified, the non-tab-specific popup is returned."
}
}
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "result",
"type": "string"
}
]
}
]
},
{
"name": "setBadgeText",
"type": "function",
"description": "Sets the badge text for the browser action. The badge is displayed on top of the icon.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Any number of characters can be passed, but only about four can fit in the space."
},
"tabId": {
"type": "integer",
"optional": true,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
]
},
{
"name": "getBadgeText",
"type": "function",
"description": "Gets the badge text of the browser action. If no tab is specified, the non-tab-specific badge text is returned.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"description": "Specify the tab to get the badge text from. If no tab is specified, the non-tab-specific badge text is returned."
}
}
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "result",
"type": "string"
}
]
}
]
},
{
"name": "setBadgeBackgroundColor",
"type": "function",
"description": "Sets the background color for the badge.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"color": {
"description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
"choices": [
{"type": "string"},
{"$ref": "ColorArray"}
]
},
"tabId": {
"type": "integer",
"optional": true,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
]
},
{
"name": "getBadgeBackgroundColor",
"type": "function",
"description": "Gets the background color of the browser action.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"description": "Specify the tab to get the badge background color from. If no tab is specified, the non-tab-specific badge background color is returned."
}
}
},
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "result",
"$ref": "ColorArray"
}
]
}
]
},
{
"name": "enable",
"type": "function",
"description": "Enables the browser action for a tab. By default, browser actions are enabled.",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want to modify the browser action."
}
]
},
{
"name": "disable",
"type": "function",
"description": "Disables the browser action for a tab.",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want to modify the browser action."
}
]
},
{
"name": "openPopup",
"type": "function",
"description": "Opens the extension popup window in the active window but does not grant tab permissions.",
"unsupported": true,
"parameters": [
{
"type": "function",
"name": "callback",
"parameters": [
{
"name": "popupView",
"type": "object",
"optional": true,
"description": "JavaScript 'window' object for the popup window if it was succesfully opened.",
"additionalProperties": { "type": "any" }
}
]
}
]
}
],
"events": [
{
"name": "onClicked",
"type": "function",
"description": "Fired when a browser action icon is clicked. This event will not fire if the browser action has a popup.",
"parameters": [
{
"name": "tab",
"$ref": "tabs.Tab"
}
]
}
]
}
]

Просмотреть файл

@ -4,5 +4,6 @@
browser.jar:
content/browser/schemas/bookmarks.json
content/browser/schemas/browser_action.json
content/browser/schemas/tabs.json
content/browser/schemas/windows.json

Просмотреть файл

@ -29,25 +29,25 @@ add_task(function* testTabSwitchContext() {
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2",
"badge": "2",
"badgeBackgroundColor": [0xff, 0, 0],
"badgeBackgroundColor": [0xff, 0, 0, 0xff],
"disabled": true },
{ "icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default-2.html"),
"title": "Default Title 2",
"badge": "d2",
"badgeBackgroundColor": [0, 0xff, 0],
"badgeBackgroundColor": [0, 0xff, 0, 0xff],
"disabled": true },
{ "icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default-2.html"),
"title": "Default Title 2",
"badge": "d2",
"badgeBackgroundColor": [0, 0xff, 0],
"badgeBackgroundColor": [0, 0xff, 0, 0xff],
"disabled": false },
{ "icon": browser.runtime.getURL("default-2.png"),
"popup": browser.runtime.getURL("default-2.html"),
"title": "Default Title 2",
"badge": "d2",
"badgeBackgroundColor": [0, 0xff, 0] },
"badgeBackgroundColor": [0, 0xff, 0, 0xff] },
];
let tabs = [];
@ -83,7 +83,7 @@ add_task(function* testTabSwitchContext() {
browser.browserAction.setPopup({ tabId, popup: "2.html" });
browser.browserAction.setTitle({ tabId, title: "Title 2" });
browser.browserAction.setBadgeText({ tabId, text: "2" });
browser.browserAction.setBadgeBackgroundColor({ tabId, color: [0xff, 0, 0] });
browser.browserAction.setBadgeBackgroundColor({ tabId, color: [0xff, 0, 0, 0xff] });
browser.browserAction.disable(tabId);
expectDefaults(details[0]).then(() => {
@ -116,7 +116,7 @@ add_task(function* testTabSwitchContext() {
browser.browserAction.setPopup({ popup: "default-2.html" });
browser.browserAction.setTitle({ title: "Default Title 2" });
browser.browserAction.setBadgeText({ text: "d2" });
browser.browserAction.setBadgeBackgroundColor({ color: [0, 0xff, 0] });
browser.browserAction.setBadgeBackgroundColor({ color: [0, 0xff, 0, 0xff] });
browser.browserAction.disable();
expectDefaults(details[3]).then(() => {
expect(details[3]);

Просмотреть файл

@ -614,6 +614,7 @@ BrowserGlue.prototype = {
ExtensionManagement.registerScript("chrome://browser/content/ext-bookmarks.js");
ExtensionManagement.registerSchema("chrome://browser/content/schemas/bookmarks.json");
ExtensionManagement.registerSchema("chrome://browser/content/schemas/browser_action.json");
ExtensionManagement.registerSchema("chrome://browser/content/schemas/tabs.json");
ExtensionManagement.registerSchema("chrome://browser/content/schemas/windows.json");

Просмотреть файл

@ -9,6 +9,11 @@ const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
instanceOf,
} = ExtensionUtils;
this.EXPORTED_SYMBOLS = ["Schemas"];
/* globals Schemas */
@ -127,6 +132,8 @@ class ChoiceType extends Type {
return r;
}
}
return {error: "No valid choice"};
}
checkBaseType(baseType) {
@ -209,21 +216,12 @@ class StringType extends Type {
}
}
class UnrestrictedObjectType extends Type {
normalize(value) {
return this.normalizeBase("object", value);
}
checkBaseType(baseType) {
return baseType == "object";
}
}
class ObjectType extends Type {
constructor(properties, additionalProperties) {
constructor(properties, additionalProperties, isInstanceOf) {
super();
this.properties = properties;
this.additionalProperties = additionalProperties;
this.isInstanceOf = isInstanceOf;
}
checkBaseType(baseType) {
@ -236,6 +234,21 @@ class ObjectType extends Type {
return v;
}
if (this.isInstanceOf) {
if (Object.keys(this.properties).length ||
!(this.additionalProperties instanceof AnyType)) {
throw new Error("InternalError: isInstanceOf can only be used with objects that are otherwise unrestricted");
}
if (!instanceOf(value, this.isInstanceOf)) {
return {error: `Object must be an instance of ${this.isInstanceOf}`};
}
// This is kind of a hack, but we can't normalize things that
// aren't JSON, so we just return them.
return {value};
}
let result = {};
for (let prop of Object.keys(this.properties)) {
let {type, optional, unsupported} = this.properties[prop];
@ -341,10 +354,11 @@ class BooleanType extends Type {
}
class ArrayType extends Type {
constructor(itemType, minItems) {
constructor(itemType, minItems, maxItems) {
super();
this.itemType = itemType;
this.minItems = minItems;
this.maxItems = maxItems;
}
normalize(value) {
@ -366,6 +380,10 @@ class ArrayType extends Type {
return {error: `Array requires at least ${this.minItems} items; you have ${result.length}`};
}
if (result.length > this.maxItems) {
return {error: `Array requires at most ${this.maxItems} items; you have ${result.length}`};
}
return {value: result};
}
@ -626,12 +644,8 @@ this.Schemas = {
type.minLength || 0,
type.maxLength || Infinity);
} else if (type.type == "object") {
if (!type.properties) {
checkTypeProperties();
return new UnrestrictedObjectType();
}
let properties = {};
for (let propName of Object.keys(type.properties)) {
for (let propName of Object.keys(type.properties || {})) {
let propType = this.parseType(namespaceName, type.properties[propName],
["optional", "unsupported", "deprecated"]);
properties[propName] = {
@ -646,11 +660,12 @@ this.Schemas = {
additionalProperties = this.parseType(namespaceName, type.additionalProperties);
}
return new ObjectType(properties, additionalProperties);
checkTypeProperties("properties", "additionalProperties", "isInstanceOf");
return new ObjectType(properties, additionalProperties, type.isInstanceOf);
} else if (type.type == "array") {
checkTypeProperties("items", "minItems");
checkTypeProperties("items", "minItems", "maxItems");
return new ArrayType(this.parseType(namespaceName, type.items),
type.minItems || 0);
type.minItems || 0, type.maxItems || Infinity);
} else if (type.type == "number") {
checkTypeProperties();
return new NumberType();

Просмотреть файл

@ -123,7 +123,7 @@ let json = [
name: "quosimodo",
type: "function",
parameters: [
{name: "xyz", type: "object"},
{name: "xyz", type: "object", additionalProperties: {type: "any"}},
],
},
],