Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2017-04-24 13:13:02 +02:00
commit fc2b61e583
19 changed files with 78 additions and 828 deletions

View File

@ -236,7 +236,7 @@ public class BrowserApp extends GeckoApp
public ActionModeCompatView mActionBar;
private VideoPlayer mVideoPlayer;
private BrowserToolbar mBrowserToolbar;
private View mDoorhangerOverlay;
private View doorhangerOverlay;
// We can't name the TabStrip class because it's not included on API 9.
private TabStripInterface mTabStrip;
private ToolbarProgressView mProgressView;
@ -258,9 +258,8 @@ public class BrowserApp extends GeckoApp
public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
private static class MenuItemInfo {
public String id;
public int id;
public String label;
public int position;
public boolean checkable;
public boolean checked;
public boolean enabled = true;
@ -295,9 +294,6 @@ public class BrowserApp extends GeckoApp
// Stored value of the toolbar height, so we know when it's changed.
private int mToolbarHeight;
// The ID to use for the next addon menu item.
private int mNextAddonMenuId = 0;
private SharedPreferencesHelper mSharedPreferencesHelper;
private ReadingListHelper mReadingListHelper;
@ -736,7 +732,7 @@ public class BrowserApp extends GeckoApp
mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
mDoorhangerOverlay = findViewById(R.id.doorhanger_overlay);
doorhangerOverlay = findViewById(R.id.doorhanger_overlay);
EventDispatcher.getInstance().registerGeckoThreadListener(this,
"Search:Keyword",
@ -1799,7 +1795,8 @@ public class BrowserApp extends GeckoApp
break;
case "Menu:Update":
updateAddonMenuItem(message.getString("id"), message.getBundle("options"));
updateAddonMenuItem(message.getInt("id") + ADDON_MENU_OFFSET,
message.getBundle("options"));
break;
case "Menu:Add":
@ -1809,8 +1806,7 @@ public class BrowserApp extends GeckoApp
Log.e(LOGTAG, "Invalid menu item name");
return;
}
info.id = message.getString("id");
info.position = ADDON_MENU_OFFSET + mNextAddonMenuId;
info.id = message.getInt("id") + ADDON_MENU_OFFSET;
info.checked = message.getBoolean("checked", false);
info.enabled = message.getBoolean("enabled", true);
info.visible = message.getBoolean("visible", true);
@ -1818,11 +1814,10 @@ public class BrowserApp extends GeckoApp
final int parent = message.getInt("parent", 0);
info.parent = parent <= 0 ? parent : parent + ADDON_MENU_OFFSET;
addAddonMenuItem(info);
mNextAddonMenuId++;
break;
case "Menu:Remove":
removeAddonMenuItem(message.getString("id"));
removeAddonMenuItem(message.getInt("id") + ADDON_MENU_OFFSET);
break;
case "LightweightTheme:Update":
@ -3093,13 +3088,13 @@ public class BrowserApp extends GeckoApp
}
}
final MenuItem item = destination.add(Menu.NONE, info.position, Menu.NONE, info.label);
final MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
final GeckoBundle data = new GeckoBundle(1);
data.putString("item", info.id);
data.putInt("item", info.id - ADDON_MENU_OFFSET);
EventDispatcher.getInstance().dispatch("Menu:Clicked", data);
return true;
}
@ -3129,38 +3124,30 @@ public class BrowserApp extends GeckoApp
addAddonMenuItemToMenu(mMenu, info);
}
private void removeAddonMenuItem(String id) {
int position = -1;
private void removeAddonMenuItem(int id) {
// Remove add-on menu item from cache, if available.
if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
for (MenuItemInfo item : mAddonMenuItemsCache) {
if (item.id.equals(id)) {
position = item.position;
mAddonMenuItemsCache.remove(item);
break;
}
if (item.id == id) {
mAddonMenuItemsCache.remove(item);
break;
}
}
}
if (mMenu == null || position == -1)
if (mMenu == null)
return;
final MenuItem menuItem = mMenu.findItem(position);
if (menuItem != null) {
mNextAddonMenuId--;
mMenu.removeItem(position);
}
final MenuItem menuItem = mMenu.findItem(id);
if (menuItem != null)
mMenu.removeItem(id);
}
private void updateAddonMenuItem(String id, final GeckoBundle options) {
int position = -1;
private void updateAddonMenuItem(int id, final GeckoBundle options) {
// Set attribute for the menu item in cache, if available
if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
for (MenuItemInfo item : mAddonMenuItemsCache) {
if (item.id == id) {
position = item.position;
item.label = options.getString("name", item.label);
item.checkable = options.getBoolean("checkable", item.checkable);
item.checked = options.getBoolean("checked", item.checked);
@ -3172,11 +3159,11 @@ public class BrowserApp extends GeckoApp
}
}
if (mMenu == null || position == -1) {
if (mMenu == null) {
return;
}
final MenuItem menuItem = mMenu.findItem(position);
final MenuItem menuItem = mMenu.findItem(id);
if (menuItem != null) {
menuItem.setTitle(options.getString("name", menuItem.getTitle().toString()));
menuItem.setCheckable(options.getBoolean("checkable", menuItem.isCheckable()));
@ -4024,7 +4011,7 @@ public class BrowserApp extends GeckoApp
@Override
public View getDoorhangerOverlay() {
return mDoorhangerOverlay;
return doorhangerOverlay;
}
public SearchEngineManager getSearchEngineManager() {

View File

@ -2262,6 +2262,7 @@ var NativeWindow = {
menu: {
_callbacks: [],
_menuId: 1,
toolsMenuID: -1,
add: function() {
let options;
@ -2278,26 +2279,25 @@ var NativeWindow = {
}
options.type = "Menu:Add";
let uuid = uuidgen.generateUUID().toString();
options.id = uuid;
options.id = this._menuId;
GlobalEventDispatcher.sendRequest(options);
this._callbacks[uuid] = options.callback;
return uuid;
this._callbacks[this._menuId] = options.callback;
this._menuId++;
return this._menuId - 1;
},
remove: function(uuid) {
GlobalEventDispatcher.sendRequest({ type: "Menu:Remove", id: uuid });
remove: function(aId) {
GlobalEventDispatcher.sendRequest({ type: "Menu:Remove", id: aId });
},
update: function(uuid, aOptions) {
update: function(aId, aOptions) {
if (!aOptions)
return;
GlobalEventDispatcher.sendRequest({
type: "Menu:Update",
id: uuid,
id: aId,
options: aOptions
});
}

View File

@ -47,15 +47,6 @@ extensions.on("page-shutdown", (type, context) => {
extensions.registerModules({
browserAction: {
url: "chrome://browser/content/ext-browserAction.js",
schema: "chrome://browser/content/schemas/browser_action.json",
scopes: ["addon_parent"],
manifest: ["browser_action"],
paths: [
["browserAction"],
],
},
pageAction: {
url: "chrome://browser/content/ext-pageAction.js",
schema: "chrome://browser/content/schemas/page_action.json",

View File

@ -1,78 +0,0 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
"resource://devtools/shared/event-emitter.js");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
// Import the android BrowserActions module.
XPCOMUtils.defineLazyModuleGetter(this, "BrowserActions",
"resource://gre/modules/BrowserActions.jsm");
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
class BrowserAction {
constructor(options, extension) {
this.id = `{${extension.uuid}}`;
this.name = options.default_title || extension.name;
BrowserActions.register(this);
EventEmitter.decorate(this);
}
/**
* Required by the BrowserActions module. This event will get
* called whenever the browser action is clicked on.
*/
onClicked() {
this.emit("click", tabTracker.activeTab);
}
/**
* Unregister the browser action from the BrowserActions module.
*/
shutdown() {
BrowserActions.unregister(this.id);
}
}
this.browserAction = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
let browserAction = new BrowserAction(manifest.browser_action, extension);
browserActionMap.set(extension, browserAction);
}
onShutdown(reason) {
let {extension} = this;
if (browserActionMap.has(extension)) {
browserActionMap.get(extension).shutdown();
browserActionMap.delete(extension);
}
}
getAPI(context) {
const {extension} = context;
const {tabManager} = extension;
return {
browserAction: {
onClicked: new SingletonEventManager(context, "browserAction.onClicked", fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
browserActionMap.get(extension).on("click", listener);
return () => {
browserActionMap.get(extension).off("click", listener);
};
}).api(),
},
};
}
};

View File

@ -1,4 +1,4 @@
# modules
# scripts
category webextension-scripts android chrome://browser/content/ext-android.js
category webextension-scripts utils chrome://browser/content/ext-utils.js
category webextension-scripts-addon android chrome://browser/content/ext-c-android.js

View File

@ -6,7 +6,6 @@ chrome.jar:
content/ext-android.js
content/ext-c-android.js
content/ext-c-tabs.js
content/ext-browserAction.js
content/ext-pageAction.js
content/ext-tabs.js
content/ext-utils.js

View File

@ -1,451 +0,0 @@
// 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": "manifest",
"types": [
{
"$extend": "WebExtensionManifest",
"properties": {
"browser_action": {
"type": "object",
"additionalProperties": { "$ref": "UnrecognizedProperty" },
"properties": {
"default_title": {
"type": "string",
"optional": true,
"preprocess": "localize"
},
"default_icon": {
"$ref": "IconPath",
"unsupported": true,
"optional": true
},
"default_popup": {
"type": "string",
"format": "relativeUrl",
"unsupported": true,
"optional": true,
"preprocess": "localize"
},
"browser_style": {
"type": "boolean",
"unsupported": true,
"optional": true
},
"default_area": {
"description": "Defines the location the browserAction will appear by default. The default location is navbar.",
"type": "string",
"enum": ["navbar", "menupanel", "tabstrip", "personaltoolbar"],
"unsupported": true,
"optional": true
}
},
"optional": true
}
}
}
]
},
{
"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.",
"permissions": ["manifest:browser_action"],
"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" },
"postprocess": "convertImageDataToURL",
"description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
}
],
"functions": [
{
"name": "setTitle",
"unsupported": true,
"type": "function",
"description": "Sets the title of the browser action. This shows up in the tooltip.",
"async": "callback",
"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."
}
}
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "getTitle",
"unsupported": true,
"type": "function",
"description": "Gets the title of the browser action.",
"async": "callback",
"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",
"unsupported": true,
"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.",
"async": "callback",
"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",
"unsupported": true,
"type": "function",
"description": "Sets the html document to be opened as a popup when the user clicks on the browser action's icon.",
"async": "callback",
"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."
}
}
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "getPopup",
"unsupported": true,
"type": "function",
"description": "Gets the html document set as the popup for this browser action.",
"async": "callback",
"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",
"unsupported": true,
"type": "function",
"description": "Sets the badge text for the browser action. The badge is displayed on top of the icon.",
"async": "callback",
"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."
}
}
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "getBadgeText",
"unsupported": true,
"type": "function",
"description": "Gets the badge text of the browser action. If no tab is specified, the non-tab-specific badge text is returned.",
"async": "callback",
"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",
"unsupported": true,
"type": "function",
"description": "Sets the background color for the badge.",
"async": "callback",
"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."
}
}
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "getBadgeBackgroundColor",
"unsupported": true,
"type": "function",
"description": "Gets the background color of the browser action.",
"async": "callback",
"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",
"unsupported": true,
"type": "function",
"description": "Enables the browser action for a tab. By default, browser actions are enabled.",
"async": "callback",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want to modify the browser action."
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "disable",
"unsupported": true,
"type": "function",
"description": "Disables the browser action for a tab.",
"async": "callback",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want to modify the browser action."
},
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": []
}
]
},
{
"name": "openPopup",
"unsupported": true,
"type": "function",
"description": "Opens the extension popup window in the active window but does not grant tab permissions.",
"async": "callback",
"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"
}
]
}
]
}
]

View File

@ -3,6 +3,5 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
chrome.jar:
content/schemas/browser_action.json
content/schemas/page_action.json
content/schemas/tabs.json

View File

@ -4,6 +4,5 @@ support-files =
../../../../../../toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
tags = webextensions
[test_ext_browserAction_onClicked.html]
[test_ext_pageAction.html]
[test_ext_pageAction_popup.html]

View File

@ -1,8 +1,9 @@
"use strict";
/* exported AppConstants */
/* exported isPageActionShown clickPageAction, AppConstants */
var {AppConstants} = SpecialPowers.Cu.import("resource://gre/modules/AppConstants.jsm", {});
var {PageActions} = SpecialPowers.Cu.import("resource://gre/modules/PageActions.jsm", {});
{
let chromeScript = SpecialPowers.loadChromeScript(
@ -21,3 +22,11 @@ var {AppConstants} = SpecialPowers.Cu.import("resource://gre/modules/AppConstant
}
});
}
function isPageActionShown(uuid) {
return PageActions.isShown(uuid);
}
function clickPageAction(uuid) {
PageActions.synthesizeClick(uuid);
}

View File

@ -1,94 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>BrowserAction Test</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
var {BrowserActions} = SpecialPowers.Cu.import("resource://gre/modules/BrowserActions.jsm", {});
async function background() {
browser.test.assertTrue("browserAction" in browser, "Namespace 'browserAction' exists in browser");
browser.test.assertTrue("onClicked" in browser.browserAction, "API method 'onClicked' exists in browser.browserAction");
const tabs = await browser.tabs.query({active: true, currentWindow: true});
browser.browserAction.onClicked.addListener(tab => {
browser.test.sendMessage("browser-action-clicked", tab);
});
browser.test.sendMessage("ready", tabs[0]);
}
function createExtension(name) {
return ExtensionTestUtils.loadExtension({
background,
manifest: {
"name": name,
"browser_action": {
"default_title": "Browser Action",
},
},
});
}
function* checkBrowserAction(extension, id, tab) {
ok(BrowserActions.isShown(id), "The BrowerAction should be shown");
BrowserActions.synthesizeClick(id);
const clickedTab = yield extension.awaitMessage("browser-action-clicked");
is(clickedTab.id, tab.id, "Got the expected tab id in the browserAction.onClicked event");
}
add_task(function* test_browserAction() {
const extension = createExtension("BrowserAction Extension");
yield extension.startup();
const tab = yield extension.awaitMessage("ready");
let id = `{${extension.uuid}}`;
yield checkBrowserAction(extension, id, tab);
yield extension.unload();
ok(!BrowserActions.isShown(id), "The BrowserAction should be removed after the extension unloads");
});
add_task(function* test_multiple_browserActions() {
const ext1 = createExtension("BrowserAction Extension 1");
const ext2 = createExtension("BrowserAction Extension 2");
// Start the first extension and test its browser action.
yield ext1.startup();
const tab1 = yield ext1.awaitMessage("ready");
let id1 = `{${ext1.uuid}}`;
yield checkBrowserAction(ext1, id1, tab1);
// Start the second extension and test its browser action.
yield ext2.startup();
const tab2 = yield ext2.awaitMessage("ready");
let id2 = `{${ext2.uuid}}`;
yield checkBrowserAction(ext2, id2, tab2);
// Verify that the first browser action is still active.
yield checkBrowserAction(ext1, id1, tab1);
// Unload the first extension and verify that the browser action is removed.
yield ext1.unload();
ok(!BrowserActions.isShown(id1), "The first BrowserAction should be removed after ext1 unloads");
// Verify that the second browser action is still active.
yield checkBrowserAction(ext2, id2, tab2);
// Unload the second extension and verify that the browser action is removed.
yield ext2.unload();
ok(!BrowserActions.isShown(id2), "The second BrowserAction should be removed after ext2 unloads");
});
</script>
</body>
</html>

View File

@ -13,8 +13,6 @@
<script type="text/javascript">
"use strict";
var {PageActions} = SpecialPowers.Cu.import("resource://gre/modules/PageActions.jsm", {});
let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
let image = atob(dataURI);
@ -42,7 +40,13 @@ async function background() {
browser.test.sendMessage("page-action-clicked", tab);
});
browser.test.sendMessage("ready", tabs[0]);
const extensionInfo = {
// Extract the assigned uuid from the background page url.
uuid: `{${window.location.hostname}}`,
tab: tabs[0],
};
browser.test.sendMessage("ready", extensionInfo);
}
add_task(function* test_pageAction() {
@ -68,28 +72,27 @@ add_task(function* test_pageAction() {
});
yield extension.startup();
const tab = yield extension.awaitMessage("ready");
const uuid = `{${extension.uuid}}`;
const {uuid, tab} = yield extension.awaitMessage("ready");
extension.sendMessage("pageAction-show");
yield extension.awaitMessage("page-action-shown");
ok(PageActions.isShown(uuid), "The PageAction should be shown");
ok(isPageActionShown(uuid), "The PageAction should be shown");
extension.sendMessage("pageAction-hide");
yield extension.awaitMessage("page-action-hidden");
ok(!PageActions.isShown(uuid), "The PageAction should be hidden");
ok(!isPageActionShown(uuid), "The PageAction should be hidden");
extension.sendMessage("pageAction-show");
yield extension.awaitMessage("page-action-shown");
ok(PageActions.isShown(uuid), "The PageAction should be shown");
ok(isPageActionShown(uuid), "The PageAction should be shown");
PageActions.synthesizeClick(uuid);
clickPageAction(uuid);
const clickedTab = yield extension.awaitMessage("page-action-clicked");
ok(PageActions.isShown(uuid), "The PageAction should still be shown after being clicked");
ok(isPageActionShown(uuid), "The PageAction should still be shown after being clicked");
is(clickedTab.id, tab.id, "Got the expected tab id in the pageAction.onClicked event");
yield extension.unload();
ok(!PageActions.isShown(uuid), "The PageAction should be removed after unload");
ok(!isPageActionShown(uuid), "The PageAction should be removed after unload");
});
</script>

View File

@ -17,14 +17,12 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
var {PageActions} = Cu.import("resource://gre/modules/PageActions.jsm", {});
let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
let image = atob(dataURI);
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
add_task(function* test_pageAction_withPopup() {
add_task(function* test_contentscript() {
function background() {
// TODO: Use the Tabs API to obtain the tab ids for showing pageActions.
let tabId = 1;
@ -58,7 +56,12 @@ add_task(function* test_pageAction_withPopup() {
browser.test.sendMessage("page-action-onClicked-fired");
});
browser.test.sendMessage("ready");
let extensionInfo = {
// Extract the assigned uuid from the background page url.
uuid: `{${window.location.hostname}}`,
};
browser.test.sendMessage("ready", extensionInfo);
}
function popupScript() {
@ -128,7 +131,7 @@ add_task(function* test_pageAction_withPopup() {
extension.sendMessage("page-action-enable-onClicked-listener");
yield extension.awaitMessage("page-action-onClicked-listener-enabled");
PageActions.synthesizeClick(uuid);
clickPageAction(uuid);
yield extension.awaitMessage("page-action-onClicked-fired");
extension.sendMessage("page-action-disable-onClicked-listener");
@ -136,7 +139,7 @@ add_task(function* test_pageAction_withPopup() {
} else {
ok(url.includes(name), "Calling pageAction.getPopup should return the correct popup URL when the popup is set.");
PageActions.synthesizeClick(uuid);
clickPageAction(uuid);
let location = yield extension.awaitMessage("page-action-from-popup");
ok(location.includes(name), "The popup with the correct URL should be shown.");
@ -148,12 +151,11 @@ add_task(function* test_pageAction_withPopup() {
}
yield extension.startup();
yield extension.awaitMessage("ready");
const uuid = `{${extension.uuid}}`;
let {uuid} = yield extension.awaitMessage("ready");
extension.sendMessage("page-action-show");
yield extension.awaitMessage("page-action-shown");
ok(PageActions.isShown(uuid), "The PageAction should be shown.");
ok(isPageActionShown(uuid), "The PageAction should be shown.");
yield testPopup("default.html", uuid);
yield testPopup("a.html", uuid);
@ -161,7 +163,7 @@ add_task(function* test_pageAction_withPopup() {
yield testPopup("b.html", uuid);
yield extension.unload();
ok(!PageActions.isShown(uuid), "The PageAction should be removed after unload.");
ok(!isPageActionShown(uuid), "The PageAction should be removed after unload.");
});
</script>

View File

@ -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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
this.EXPORTED_SYMBOLS = ["BrowserActions"];
var BrowserActions = {
_browserActions: {},
_initialized: false,
/**
* Registers the listeners only if they have not been initialized
* already and there is at least one browser action.
*/
_maybeRegisterListeners() {
if (!this._initialized && Object.keys(this._browserActions).length) {
this._initialized = true;
EventDispatcher.instance.registerListener(this, "Menu:Clicked");
}
},
/**
* Unregisters the listeners if they are already initizliaed and
* all of the browser actions have been removed.
*/
_maybeUnregisterListeners: function() {
if (this._initialized && !Object.keys(this._browserActions).length) {
this._initialized = false;
EventDispatcher.instance.unregisterListener(this, "Menu:Clicked");
}
},
/**
* Called when a browser action is clicked on.
* @param {string} event The name of the event, which should always
* be "Menu:Clicked".
* @param {Object} data An object containing information about the
* browser action, which in this case should contain an `item`
* property which is browser action's ID.
*/
onEvent(event, data) {
if (event !== "Menu:Clicked") {
throw new Error(`Expected "Menu:Clicked" event - received "${event}" instead`);
}
let browserAction = this._browserActions[data.item];
if (!browserAction) {
throw new Error(`No browser action found with id ${data.item}`);
}
browserAction.onClicked();
},
/**
* Registers a new browser action.
* @param {Object} browserAction The browser action to add.
*/
register(browserAction) {
EventDispatcher.instance.sendRequest({
type: "Menu:Add",
id: browserAction.id,
name: browserAction.name,
});
this._browserActions[browserAction.id] = browserAction;
this._maybeRegisterListeners();
},
/**
* Checks to see if the browser action is shown. Used for testing only.
* @param {string} id The ID of the browser action.
* @returns True if the browser action is shown; false otherwise.
*/
isShown: function(id) {
return !!this._browserActions[id];
},
/**
* Synthesizes a click on the browser action. Used for testing only.
* @param {string} id The ID of the browser action.
*/
synthesizeClick: function(id) {
let browserAction = this._browserActions[id];
if (!browserAction) {
throw new Error(`No browser action found with id ${id}`);
}
browserAction.onClicked();
},
/**
* Unregisters the browser action with the specified ID.
* @param {string} id The browser action ID.
*/
unregister(id) {
let browserAction = this._browserActions[id];
if (!browserAction) {
throw new Error(`No BrowserAction with ID ${id} was found`);
}
EventDispatcher.instance.sendRequest({
type: "Menu:Remove",
id,
});
delete this._browserActions[id];
this._maybeUnregisterListeners();
}
}

View File

@ -34,11 +34,11 @@ function resolveGeckoURI(aURI) {
var PageActions = {
_items: { },
_initialized: false,
_inited: false,
_maybeInitialize: function() {
if (!this._initialized && Object.keys(this._items).length) {
this._initialized = true;
_maybeInit: function() {
if (!this._inited && Object.keys(this._items).length > 0) {
this._inited = true;
EventDispatcher.instance.registerListener(this, [
"PageActions:Clicked",
"PageActions:LongClicked",
@ -46,9 +46,9 @@ var PageActions = {
}
},
_maybeUninitialize: function() {
if (this._initialized && !Object.keys(this._items).length) {
this._initialized = false;
_maybeUninit: function() {
if (this._inited && Object.keys(this._items).length == 0) {
this._inited = false;
EventDispatcher.instance.unregisterListener(this, [
"PageActions:Clicked",
"PageActions:LongClicked",
@ -101,7 +101,7 @@ var PageActions = {
this._items[id].longClickCallback = aOptions.longClickCallback;
}
this._maybeInitialize();
this._maybeInit();
return id;
},
@ -112,6 +112,6 @@ var PageActions = {
});
delete this._items[id];
this._maybeUninitialize();
this._maybeUninit();
}
}

View File

@ -21,7 +21,6 @@ DIRS += ['geckoview']
EXTRA_JS_MODULES += [
'Accounts.jsm',
'BrowserActions.jsm',
'dbg-browser-actors.js',
'DelayedInit.jsm',
'DownloadNotifications.jsm',

View File

@ -570,7 +570,7 @@ SpecialPowersObserverAPI.prototype = {
let id = aMessage.data.id;
let extension = this._extensions.get(id);
extension.on("startup", () => {
this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionSetId", args: [extension.id, extension.uuid]});
this._sendReply(aMessage, "SPExtensionMessage", {id, type: "extensionSetId", args: [extension.id]});
});
// Make sure the extension passes the packaging checks when

View File

@ -2001,7 +2001,6 @@ SpecialPowersAPI.prototype = {
resolveStartup();
} else if (msg.data.type == "extensionSetId") {
extension.id = msg.data.args[0];
extension.uuid = msg.data.args[1];
} else if (msg.data.type == "extensionFailed") {
state = "failed";
rejectStartup("startup failed");

View File

@ -188,7 +188,6 @@ class ExtensionWrapper {
if (extension) {
this.id = extension.id;
this.uuid = extension.uuid;
this.attachExtension(extension);
}
}