diff options
Diffstat (limited to 'mobile/android/components/extensions')
15 files changed, 782 insertions, 0 deletions
diff --git a/mobile/android/components/extensions/.eslintrc.js b/mobile/android/components/extensions/.eslintrc.js new file mode 100644 index 000000000..4b67e27b8 --- /dev/null +++ b/mobile/android/components/extensions/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + "extends": "../../../../toolkit/components/extensions/.eslintrc.js", +}; diff --git a/mobile/android/components/extensions/ext-pageAction.js b/mobile/android/components/extensions/ext-pageAction.js new file mode 100644 index 000000000..fb1c3a3f3 --- /dev/null +++ b/mobile/android/components/extensions/ext-pageAction.js @@ -0,0 +1,169 @@ +/* -*- 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 PageActions module. +XPCOMUtils.defineLazyModuleGetter(this, "PageActions", + "resource://gre/modules/PageActions.jsm"); + +Cu.import("resource://gre/modules/ExtensionUtils.jsm"); + +var { + IconDetails, + SingletonEventManager, +} = ExtensionUtils; + +// WeakMap[Extension -> PageAction] +var pageActionMap = new WeakMap(); + +function PageAction(options, extension) { + this.id = null; + + this.extension = extension; + this.icons = IconDetails.normalize({path: options.default_icon}, extension); + + this.popupUrl = options.default_popup; + + this.options = { + title: options.default_title || extension.name, + id: `{${extension.uuid}}`, + clickCallback: () => { + if (this.popupUrl) { + let win = Services.wm.getMostRecentWindow("navigator:browser"); + win.BrowserApp.addTab(this.popupUrl, { + selected: true, + parentId: win.BrowserApp.selectedTab.id, + }); + } else { + this.emit("click"); + } + }, + }; + + this.shouldShow = false; + + EventEmitter.decorate(this); +} + +PageAction.prototype = { + show(tabId, context) { + if (this.id) { + return Promise.resolve(); + } + + if (this.options.icon) { + this.id = PageActions.add(this.options); + return Promise.resolve(); + } + + this.shouldShow = true; + + // TODO(robwu): Remove dependency on contentWindow from this file. It should + // be put in a separate file called ext-c-pageAction.js. + // Note: Fennec is not going to be multi-process for the foreseaable future, + // so this layering violation has no immediate impact. However, it is should + // be done at some point. + let {contentWindow} = context.xulBrowser; + + // TODO(robwu): Why is this contentWindow.devicePixelRatio, while + // convertImageURLToDataURL uses browserWindow.devicePixelRatio? + let {icon} = IconDetails.getPreferredIcon(this.icons, this.extension, + 18 * contentWindow.devicePixelRatio); + + let browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + return IconDetails.convertImageURLToDataURL(icon, contentWindow, browserWindow).then(dataURI => { + if (this.shouldShow) { + this.options.icon = dataURI; + this.id = PageActions.add(this.options); + } + }).catch(() => { + return Promise.reject({ + message: "Failed to load PageAction icon", + }); + }); + }, + + hide(tabId) { + this.shouldShow = false; + if (this.id) { + PageActions.remove(this.id); + this.id = null; + } + }, + + setPopup(tab, url) { + // TODO: Only set the popup for the specified tab once we have Tabs API support. + this.popupUrl = url; + }, + + getPopup(tab) { + // TODO: Only return the popup for the specified tab once we have Tabs API support. + return this.popupUrl; + }, + + shutdown() { + this.hide(); + }, +}; + +/* eslint-disable mozilla/balanced-listeners */ +extensions.on("manifest_page_action", (type, directive, extension, manifest) => { + let pageAction = new PageAction(manifest.page_action, extension); + pageActionMap.set(extension, pageAction); +}); + +extensions.on("shutdown", (type, extension) => { + if (pageActionMap.has(extension)) { + pageActionMap.get(extension).shutdown(); + pageActionMap.delete(extension); + } +}); +/* eslint-enable mozilla/balanced-listeners */ + +extensions.registerSchemaAPI("pageAction", "addon_parent", context => { + let {extension} = context; + return { + pageAction: { + onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => { + let listener = (event) => { + fire(); + }; + pageActionMap.get(extension).on("click", listener); + return () => { + pageActionMap.get(extension).off("click", listener); + }; + }).api(), + + show(tabId) { + return pageActionMap.get(extension) + .show(tabId, context) + .then(() => {}); + }, + + hide(tabId) { + pageActionMap.get(extension).hide(tabId); + return Promise.resolve(); + }, + + setPopup(details) { + // TODO: Use the Tabs API to get the tab from details.tabId. + let tab = null; + let url = details.popup && context.uri.resolve(details.popup); + pageActionMap.get(extension).setPopup(tab, url); + }, + + getPopup(details) { + // TODO: Use the Tabs API to get the tab from details.tabId. + let tab = null; + let popup = pageActionMap.get(extension).getPopup(tab); + return Promise.resolve(popup); + }, + }, + }; +}); diff --git a/mobile/android/components/extensions/extensions-mobile.manifest b/mobile/android/components/extensions/extensions-mobile.manifest new file mode 100644 index 000000000..f15540d62 --- /dev/null +++ b/mobile/android/components/extensions/extensions-mobile.manifest @@ -0,0 +1,5 @@ +# scripts +category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js + +# schemas +category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
\ No newline at end of file diff --git a/mobile/android/components/extensions/jar.mn b/mobile/android/components/extensions/jar.mn new file mode 100644 index 000000000..a3d2b8de8 --- /dev/null +++ b/mobile/android/components/extensions/jar.mn @@ -0,0 +1,6 @@ +# 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/. + +chrome.jar: + content/ext-pageAction.js
\ No newline at end of file diff --git a/mobile/android/components/extensions/moz.build b/mobile/android/components/extensions/moz.build new file mode 100644 index 000000000..0953fcefc --- /dev/null +++ b/mobile/android/components/extensions/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JAR_MANIFESTS += ['jar.mn'] + +EXTRA_COMPONENTS += [ + 'extensions-mobile.manifest', +] + +DIRS += ['schemas'] + +MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini'] +MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini'] diff --git a/mobile/android/components/extensions/schemas/jar.mn b/mobile/android/components/extensions/schemas/jar.mn new file mode 100644 index 000000000..1a587ce20 --- /dev/null +++ b/mobile/android/components/extensions/schemas/jar.mn @@ -0,0 +1,6 @@ +# 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/. + +chrome.jar: + content/schemas/page_action.json
\ No newline at end of file diff --git a/mobile/android/components/extensions/schemas/moz.build b/mobile/android/components/extensions/schemas/moz.build new file mode 100644 index 000000000..eb4454d28 --- /dev/null +++ b/mobile/android/components/extensions/schemas/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/mobile/android/components/extensions/schemas/page_action.json b/mobile/android/components/extensions/schemas/page_action.json new file mode 100644 index 000000000..5e9280922 --- /dev/null +++ b/mobile/android/components/extensions/schemas/page_action.json @@ -0,0 +1,239 @@ +// 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": { + "page_action": { + "type": "object", + "additionalProperties": { "$ref": "UnrecognizedProperty" }, + "properties": { + "default_title": { + "type": "string", + "optional": true, + "preprocess": "localize" + }, + "default_icon": { + "$ref": "IconPath", + "optional": true + }, + "default_popup": { + "type": "string", + "format": "relativeUrl", + "optional": true, + "preprocess": "localize" + }, + "browser_style": { + "type": "boolean", + "optional": true + } + }, + "optional": true + } + } + } + ] + }, + { + "namespace": "pageAction", + "description": "Use the <code>browser.pageAction</code> API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages.", + "permissions": ["manifest:page_action"], + "types": [ + { + "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": "show", + "type": "function", + "description": "Shows the page action. The page action is shown whenever the tab is selected.", + "async": "callback", + "parameters": [ + {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "hide", + "type": "function", + "description": "Hides the page action.", + "async": "callback", + "parameters": [ + {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "setTitle", + "unsupported": true, + "type": "function", + "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.", + "parameters": [ + { + "name": "details", + "type": "object", + "properties": { + "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}, + "title": {"type": "string", "description": "The tooltip string."} + } + } + ] + }, + { + "name": "getTitle", + "unsupported": true, + "type": "function", + "description": "Gets the title of the page action.", + "async": "callback", + "parameters": [ + { + "name": "details", + "type": "object", + "properties": { + "tabId": { + "type": "integer", + "description": "Specify the tab to get the title from." + } + } + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "result", + "type": "string" + } + ] + } + ] + }, + { + "name": "setIcon", + "unsupported": true, + "type": "function", + "description": "Sets the icon for the page 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": { + "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}, + "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}'" + } + } + }, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "setPopup", + "type": "function", + "async": "callback", + "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.", + "parameters": [ + { + "name": "details", + "type": "object", + "properties": { + "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}, + "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", + "type": "function", + "description": "Gets the html document set as the popup for this page action.", + "async": "callback", + "parameters": [ + { + "name": "details", + "type": "object", + "properties": { + "tabId": { + "type": "integer", + "description": "Specify the tab to get the popup from." + } + } + }, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] + } + ] + } + ], + "events": [ + { + "name": "onClicked", + "type": "function", + "description": "Fired when a page action icon is clicked. This event will not fire if the page action has a popup.", + "parameters": [ + { + "name": "tab", + "$ref": "tabs.Tab" + } + ] + } + ] + } +] diff --git a/mobile/android/components/extensions/test/mochitest/.eslintrc.js b/mobile/android/components/extensions/test/mochitest/.eslintrc.js new file mode 100644 index 000000000..5f9059e18 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/.eslintrc.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + "extends": "../../../../../../toolkit/components/extensions/test/mochitest/.eslintrc.js", + + "globals": { + "isPageActionShown": true, + "clickPageAction": true, + }, +}; diff --git a/mobile/android/components/extensions/test/mochitest/chrome.ini b/mobile/android/components/extensions/test/mochitest/chrome.ini new file mode 100644 index 000000000..e19ddf393 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/chrome.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + head.js +tags = webextensions + +[test_ext_pageAction.html] +[test_ext_pageAction_popup.html] diff --git a/mobile/android/components/extensions/test/mochitest/head.js b/mobile/android/components/extensions/test/mochitest/head.js new file mode 100644 index 000000000..be9683682 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/head.js @@ -0,0 +1,15 @@ +"use strict"; + +/* exported isPageActionShown clickPageAction */ + +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/PageActions.jsm"); + +function isPageActionShown(uuid) { + return PageActions.isShown(uuid); +} + +function clickPageAction(uuid) { + PageActions.synthesizeClick(uuid); +} diff --git a/mobile/android/components/extensions/test/mochitest/mochitest.ini b/mobile/android/components/extensions/test/mochitest/mochitest.ini new file mode 100644 index 000000000..59ef4bd20 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/mochitest.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + ../../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js +tags = webextensions + +[test_ext_all_apis.html] diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_all_apis.html b/mobile/android/components/extensions/test/mochitest/test_ext_all_apis.html new file mode 100644 index 000000000..aec3eb7c1 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/test_ext_all_apis.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <meta charset="utf-8"> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<script> +"use strict"; +/* exported expectedContentApisTargetSpecific, expectedBackgroundApisTargetSpecific */ +let expectedContentApisTargetSpecific = [ +]; + +let expectedBackgroundApisTargetSpecific = [ +]; +</script> +<script src="test_ext_all_apis.js"></script> +</body> +</html> diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html new file mode 100644 index 000000000..b13c551bd --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>PageAction 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"; + +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; + +function background() { + browser.test.assertTrue("pageAction" in browser, "Namespace 'pageAction' exists in browser"); + browser.test.assertTrue("show" in browser.pageAction, "API method 'show' exists in browser.pageAction"); + + // TODO: Use the Tabs API to obtain the tab ids for showing pageActions. + let tabId = 1; + browser.test.onMessage.addListener(msg => { + if (msg === "pageAction-show") { + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("page-action-shown"); + }); + } else if (msg === "pageAction-hide") { + browser.pageAction.hide(tabId).then(() => { + browser.test.sendMessage("page-action-hidden"); + }); + } + }); + + browser.pageAction.onClicked.addListener(tab => { + // TODO: Make sure we get the correct tab once basic tabs support is added. + browser.test.sendMessage("page-action-clicked"); + }); + + let extensionInfo = { + // Extract the assigned uuid from the background page url. + uuid: `{${window.location.hostname}}`, + }; + + browser.test.sendMessage("ready", extensionInfo); +} + +add_task(function* test_pageAction() { + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + "name": "PageAction Extension", + "page_action": { + "default_title": "Page Action", + "default_icon": { + "18": "extension.png", + }, + }, + "applications": { + "gecko": { + "id": "foo@bar.com", + }, + }, + }, + files: { + "extension.png": IMAGE_ARRAYBUFFER, + }, + }); + + yield extension.startup(); + let {uuid} = yield extension.awaitMessage("ready"); + + extension.sendMessage("pageAction-show"); + yield extension.awaitMessage("page-action-shown"); + ok(isPageActionShown(uuid), "The PageAction should be shown"); + + extension.sendMessage("pageAction-hide"); + yield extension.awaitMessage("page-action-hidden"); + ok(!isPageActionShown(uuid), "The PageAction should be hidden"); + + extension.sendMessage("pageAction-show"); + yield extension.awaitMessage("page-action-shown"); + ok(isPageActionShown(uuid), "The PageAction should be shown"); + + clickPageAction(uuid); + yield extension.awaitMessage("page-action-clicked"); + ok(isPageActionShown(uuid), "The PageAction should still be shown after being clicked"); + + yield extension.unload(); + ok(!isPageActionShown(uuid), "The PageAction should be removed after unload"); +}); +</script> + +</body> +</html> diff --git a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html new file mode 100644 index 000000000..89edc7c29 --- /dev/null +++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html @@ -0,0 +1,169 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>PageAction 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"; + +Cu.import("resource://gre/modules/Services.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_contentscript() { + function background() { + // TODO: Use the Tabs API to obtain the tab ids for showing pageActions. + let tabId = 1; + let onClickedListenerEnabled = false; + + browser.test.onMessage.addListener((msg, details) => { + if (msg === "page-action-show") { + // TODO: switch to using .show(tabId).then(...) once bug 1270742 lands. + browser.pageAction.show(tabId).then(() => { + browser.test.sendMessage("page-action-shown"); + }); + } else if (msg == "page-action-set-popup") { + browser.pageAction.setPopup({popup: details.name, tabId: tabId}).then(() => { + browser.test.sendMessage("page-action-popup-set"); + }); + } else if (msg == "page-action-get-popup") { + browser.pageAction.getPopup({tabId: tabId}).then(url => { + browser.test.sendMessage("page-action-got-popup", url); + }); + } else if (msg == "page-action-enable-onClicked-listener") { + onClickedListenerEnabled = true; + browser.test.sendMessage("page-action-onClicked-listener-enabled"); + } else if (msg == "page-action-disable-onClicked-listener") { + onClickedListenerEnabled = false; + browser.test.sendMessage("page-action-onClicked-listener-disabled"); + } + }); + + browser.pageAction.onClicked.addListener(tab => { + browser.test.assertTrue(onClickedListenerEnabled, "The onClicked listener should only fire when it is enabled."); + browser.test.sendMessage("page-action-onClicked-fired"); + }); + + let extensionInfo = { + // Extract the assigned uuid from the background page url. + uuid: `{${window.location.hostname}}`, + }; + + browser.test.sendMessage("ready", extensionInfo); + } + + function popupScript() { + window.onload = () => { + browser.test.sendMessage("page-action-from-popup", location.href); + }; + browser.test.onMessage.addListener((msg, details) => { + if (msg == "page-action-close-popup") { + if (details.location == location.href) { + window.close(); + } + } + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { + "name": "PageAction Extension", + "page_action": { + "default_title": "Page Action", + "default_popup": "default.html", + "default_icon": { + "18": "extension.png", + }, + }, + }, + files: { + "default.html": `<html><head><meta charset="utf-8"><script src="popup.js"><\/script></head></html>`, + "extension.png": IMAGE_ARRAYBUFFER, + "a.html": `<html><head><meta charset="utf-8"><script src="popup.js"><\/script></head></html>`, + "b.html": `<html><head><meta charset="utf-8"><script src="popup.js"><\/script></head></html>`, + "popup.js": popupScript, + }, + }); + + let tabClosedPromise = () => { + return new Promise(resolve => { + let chromeWin = Services.wm.getMostRecentWindow("navigator:browser"); + let BrowserApp = chromeWin.BrowserApp; + + let tabCloseListener = (event) => { + BrowserApp.deck.removeEventListener("TabClose", tabCloseListener, false); + let browser = event.target; + let url = browser.currentURI.spec; + resolve(url); + }; + + BrowserApp.deck.addEventListener("TabClose", tabCloseListener, false); + }); + }; + + function* testPopup(name, uuid) { + // We don't need to set the popup when testing default_popup. + if (name != "default.html") { + extension.sendMessage("page-action-set-popup", {name}); + yield extension.awaitMessage("page-action-popup-set"); + } + + extension.sendMessage("page-action-get-popup"); + let url = yield extension.awaitMessage("page-action-got-popup"); + + if (name == "") { + ok(url == name, "Calling pageAction.getPopup should return an empty string when the popup is not set."); + + // The onClicked listener should get called when the popup is set to an empty string. + extension.sendMessage("page-action-enable-onClicked-listener"); + yield extension.awaitMessage("page-action-onClicked-listener-enabled"); + + clickPageAction(uuid); + yield extension.awaitMessage("page-action-onClicked-fired"); + + extension.sendMessage("page-action-disable-onClicked-listener"); + yield extension.awaitMessage("page-action-onClicked-listener-disabled"); + } else { + ok(url.includes(name), "Calling pageAction.getPopup should return the correct popup URL when the popup is set."); + + clickPageAction(uuid); + let location = yield extension.awaitMessage("page-action-from-popup"); + ok(location.includes(name), "The popup with the correct URL should be shown."); + + extension.sendMessage("page-action-close-popup", {location}); + + url = yield tabClosedPromise(); + ok(url.includes(name), "The tab for the popup should be closed."); + } + } + + yield extension.startup(); + let {uuid} = yield extension.awaitMessage("ready"); + + extension.sendMessage("page-action-show"); + yield extension.awaitMessage("page-action-shown"); + ok(isPageActionShown(uuid), "The PageAction should be shown."); + + yield testPopup("default.html", uuid); + yield testPopup("a.html", uuid); + yield testPopup("", uuid); + yield testPopup("b.html", uuid); + + yield extension.unload(); + ok(!isPageActionShown(uuid), "The PageAction should be removed after unload."); +}); +</script> + +</body> +</html> |