diff --git a/README.md b/README.md index a4e89eb..b3aede8 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -chrome-extensions-example -========================= +chrome-extensions-examples +========================== -The [Chrome Extensions examples](http://developer.chrome.com/extensions/samples.html) did not +The [Chrome Extensions examples](http://developer.chrome.com/extensions/samples) did not exist as a Git repository, and browsing both the samples page and the VCViewer did not seem particularly handy. So, I decided to scrape the content into this repository for easier browsing and (possible) editing. -If you would like to clone a part of this repository, use git +If you would like to clone a part of this repository, use git [sparse checkouts](http://jasonkarns.com/blog/subdirectory-checkouts-with-git-sparse-checkout/). -You can find the scraper used to generate this repository (except for a `git init` and push) +You can find the scraper used to generate this repository (except for a `git init` and push) on [github](https://github.com/orbitbot/chrome-extension-scraper). @@ -42,13 +42,11 @@ Example projects * [Chromium IRC App](/app_2/) * [Chromium Search](/chrome_search/) * [Console TTS Engine](/console_tts_engine/) -* [Content Script Cross-Domain XMLHttpRequest Example](/contentscript_xhr/) * [Content settings](/contentSettings/) * [Context Menus Sample](/basic_2/) * [Context Menus Sample (with Event Page)](/event_page/) * [Cookie API Test Extension](/cookies/) * [Desktop Capture Example](/desktopCapture/) -* [Download Filename Controller](/download_filename_controller/) * [Download Manager Button](/download_manager/) * [Download Selected Links](/download_links/) * [Download and Open Button](/download_open/) @@ -56,7 +54,10 @@ Example projects * [Email this page (by Google)](/email_this_page/) * [Event Page Example](/basic_4/) * [Event Tracking with Google Analytics](/analytics/) +* [Fake Archive Handler App](/archive/) +* [File System Provider API Extension Example](/basic_5/) * [FirePHP for Chrome](/chrome-firephp/) +* [Getting started example](/getstarted/) * [Google Calendar Checker (by Google)](/calendar/) * [Google Document List Viewer](/gdocs/) * [Google Mail Checker](/gmail/) @@ -67,6 +68,7 @@ Example projects * [Keep Awake](/power/) * [Keyboard Pin](/pin/) * [Live HTTP headers](/live-headers/) +* [Managed Bookmarks](/managed_bookmarks/) * [Mappy](/mappy/) * [Merge Windows](/merge_windows/) * [Message Timer](/timer/) @@ -80,8 +82,6 @@ Example projects * [News Reader (by Google)](/news/) * [Notification Demo](/notifications/) * [Omnibox Example](/simple-example/) -* [One-click Kittens](/getstarted/) -* [Page Benchmarker](/benchmark/) * [Page Redder](/make_page_red/) * [Page action by URL](/pageaction_by_url/) * [Page action by content](/pageaction_by_content/) @@ -92,7 +92,6 @@ Example projects * [Sample - OAuth Contacts](/oauth_contacts/) * [Sample Extension Commands extension](/commands/) * [Sandboxed Frame](/sandbox/) -* [SandwichBar](/sandwichbar/) * [Show Tabs in Process](/show_tabs/) * [Simple Background App](/background-simple/) * [Speak Selection](/speak_selection/) @@ -101,12 +100,13 @@ Example projects * [TTS Demo](/ttsdemo/) * [Tab Inspector](/inspector/) * [Tab Shortcuts](/tab_shortcuts/) +* [Tabs Zoom API Demo](/zoom/) * [Talking Alarm Clock](/talking_alarm_clock/) -* [Test IME](/basic_5/) +* [Test IME](/basic_6/) * [Test Screenshot Extension](/screenshot/) * [Top Chrome Extension Questions](/extension-questions/) -* [Top Sites](/basic_6/) +* [Top Sites](/basic_7/) * [Typed URL History](/showHistory/) -* [WebNavigation Tech Demo](/basic_7/) +* [WebNavigation Tech Demo](/basic_8/) * [`extension.isAllowedFileSchemeAccess` and `extension.isAllowedIncognitoAccess` Example](/isAllowedAccess/) * [iGoogle new tab page](/override_igoogle/) \ No newline at end of file diff --git a/allowThirdPartyCookies/README.md b/allowThirdPartyCookies/README.md index eda2f7b..56fed86 100644 --- a/allowThirdPartyCookies/README.md +++ b/allowThirdPartyCookies/README.md @@ -11,5 +11,4 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [extension.isAllowedIncognitoAccess](http://developer.chrome.com/extensions/extension.html#method-isAllowedIncognitoAccess) -* [privacy.websites.thirdPartyCookiesAllowed](http://developer.chrome.com/extensions/privacy.html#property-websites-thirdPartyCookiesAllowed) \ No newline at end of file +* [extension.isAllowedIncognitoAccess](https://developer.chrome.com/extensions/extension#method-isAllowedIncognitoAccess) \ No newline at end of file diff --git a/app/README.md b/app/README.md index dd7eb65..88f0793 100644 --- a/app/README.md +++ b/app/README.md @@ -11,5 +11,4 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [runtime.connectNative](http://developer.chrome.com/extensions/runtime.html#method-connectNative) -* [runtime.lastError.message](http://developer.chrome.com/extensions/runtime.html#property-lastError-message) \ No newline at end of file +* [runtime.connectNative](https://developer.chrome.com/extensions/runtime#method-connectNative) \ No newline at end of file diff --git a/app_1/README.md b/app_1/README.md index e7e6b50..b70b216 100644 --- a/app_1/README.md +++ b/app_1/README.md @@ -11,9 +11,3 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [app.runtime](http://developer.chrome.com/extensions/app.runtime.html) -* [app.runtime.onLaunched](http://developer.chrome.com/extensions/app.runtime.html#event-onLaunched) -* [app.runtime.onRestarted](http://developer.chrome.com/extensions/app.runtime.html#event-onRestarted) -* [app.window.create](http://developer.chrome.com/extensions/app.window.html#method-create) -* [storage.StorageArea.get](http://developer.chrome.com/extensions/storage.html#method-StorageArea-get) -* [storage.StorageArea.set](http://developer.chrome.com/extensions/storage.html#method-StorageArea-set) \ No newline at end of file diff --git a/app_1/controller.js b/app_1/controller.js index a3998d7..18a13e9 100644 --- a/app_1/controller.js +++ b/app_1/controller.js @@ -4,36 +4,29 @@ * found in the LICENSE file. **/ -// Checking for "chrome.app.runtime" availability allows this Chrome app code to -// be tested in a regular web page (like tests/manual.html). Checking for -// "chrome" and "chrome.app" availability further allows this code to be tested -// in non-Chrome browsers, which is useful for example to test touch support -// with a non-Chrome touch device. +// Checking for "chrome" availability allows this app code to be tested in +// non-Chrome browsers, which is useful for example to test touch support with +// a non-Chrome touch device. +// Checking for "chrome.shell" allows testing under app_shell, which does not +// have chrome.app APIs. +// Checking for "chrome.app.runtime" availability allows testing in a regular +// web page (like tests/manual.html). if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { var showCalculatorWindow = function () { chrome.app.window.create('calculator.html', { - defaultWidth: 243, minWidth: 243, maxWidth: 243, - defaultHeight: 380, minHeight: 380, maxHeight: 380, + innerBounds: { + width: 243, minWidth: 243, maxWidth: 243, + height: 380, minHeight: 380, maxHeight: 380 + }, id: 'calculator' }, function(appWindow) { appWindow.contentWindow.onload = function() { new Controller(new Model(9), new View(appWindow.contentWindow)); }; - - chrome.storage.local.set({windowVisible: true}); - appWindow.onClosed.addListener(function() { - chrome.storage.local.set({windowVisible: false}); - }); }); } chrome.app.runtime.onLaunched.addListener(showCalculatorWindow); - chrome.app.runtime.onRestarted.addListener(function() { - chrome.storage.local.get('windowVisible', function(data) { - if (data.windowVisible) - showCalculatorWindow(); - }); - }); } function Controller(model, view) { @@ -51,21 +44,21 @@ function Controller(model, view) { /** @private */ Controller.prototype.defineInputs_ = function() { var inputs = {byButton: {}, byKey: {}}; - inputs.byButton['zero'] = inputs.byKey['48'] = '0'; - inputs.byButton['one'] = inputs.byKey['49'] = '1'; - inputs.byButton['two'] = inputs.byKey['50'] = '2'; - inputs.byButton['three'] = inputs.byKey['51'] = '3'; - inputs.byButton['four'] = inputs.byKey['52'] = '4'; - inputs.byButton['five'] = inputs.byKey['53'] = '5'; - inputs.byButton['six'] = inputs.byKey['54'] = '6'; - inputs.byButton['seven'] = inputs.byKey['55'] = '7'; - inputs.byButton['eight'] = inputs.byKey['56'] = '8'; - inputs.byButton['nine'] = inputs.byKey['57'] = '9'; - inputs.byButton['point'] = inputs.byKey['190'] = '.'; - inputs.byButton['add'] = inputs.byKey['^187'] = '+'; - inputs.byButton['subtract'] = inputs.byKey['189'] = '-'; - inputs.byButton['multiply'] = inputs.byKey['^56'] = '*'; - inputs.byButton['divide'] = inputs.byKey['191'] = '/'; + inputs.byButton['zero'] = inputs.byKey['48'] = inputs.byKey['96'] = '0'; + inputs.byButton['one'] = inputs.byKey['49'] = inputs.byKey['97'] = '1'; + inputs.byButton['two'] = inputs.byKey['50'] = inputs.byKey['98'] = '2'; + inputs.byButton['three'] = inputs.byKey['51'] = inputs.byKey['99'] = '3'; + inputs.byButton['four'] = inputs.byKey['52'] = inputs.byKey['100'] = '4'; + inputs.byButton['five'] = inputs.byKey['53'] = inputs.byKey['101'] = '5'; + inputs.byButton['six'] = inputs.byKey['54'] = inputs.byKey['102'] = '6'; + inputs.byButton['seven'] = inputs.byKey['55'] = inputs.byKey['103'] = '7'; + inputs.byButton['eight'] = inputs.byKey['56'] = inputs.byKey['104'] = '8'; + inputs.byButton['nine'] = inputs.byKey['57'] = inputs.byKey['105'] = '9'; + inputs.byButton['point'] = inputs.byKey['190'] = inputs.byKey['110'] = '.'; + inputs.byButton['add'] = inputs.byKey['^187'] = inputs.byKey['107'] = '+'; + inputs.byButton['subtract'] = inputs.byKey['189'] = inputs.byKey['109'] = '-'; + inputs.byButton['multiply'] = inputs.byKey['^56'] = inputs.byKey['106'] = '*'; + inputs.byButton['divide'] = inputs.byKey['191'] = inputs.byKey['111'] = '/'; inputs.byButton['equals'] = inputs.byKey['187'] = inputs.byKey['13'] = '='; inputs.byButton['negate'] = inputs.byKey['32'] = '+ / -'; inputs.byButton['clear'] = inputs.byKey['67'] = 'AC'; diff --git a/app_1/manifest.json b/app_1/manifest.json index 76c570d..09b249e 100644 --- a/app_1/manifest.json +++ b/app_1/manifest.json @@ -3,11 +3,10 @@ "description": "A simple calculator.", "manifest_version": 2, "minimum_chrome_version": "23", - "version": "1.3.2", + "version": "1.3.3", "app": {"background": {"scripts": ["model.js", "view.js", "controller.js"]}}, "icons": { "16": "images/icon-16x16.png", "128": "images/icon-128x128.png" - }, - "permissions": ["storage"] + } } diff --git a/app_launcher/README.md b/app_launcher/README.md index db1b3f6..fdaa8c5 100644 --- a/app_launcher/README.md +++ b/app_launcher/README.md @@ -11,7 +11,7 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [extension.getURL](http://developer.chrome.com/extensions/extension.html#method-getURL) -* [management.getAll](http://developer.chrome.com/extensions/management.html#method-getAll) -* [management.launchApp](http://developer.chrome.com/extensions/management.html#method-launchApp) -* [tabs.create](http://developer.chrome.com/extensions/tabs.html#method-create) \ No newline at end of file +* [extension.getURL](https://developer.chrome.com/extensions/extension#method-getURL) +* [management.getAll](https://developer.chrome.com/extensions/management#method-getAll) +* [management.launchApp](https://developer.chrome.com/extensions/management#method-launchApp) +* [tabs.create](https://developer.chrome.com/extensions/tabs#method-create) \ No newline at end of file diff --git a/archive/README.md b/archive/README.md new file mode 100644 index 0000000..8651c19 --- /dev/null +++ b/archive/README.md @@ -0,0 +1,25 @@ + +Fake Archive Handler App +======= + +Demonstrate File System Provider API usage for apps. + +[Zipfile](http://developer.chrome.com/extensions/examples/api/fileSystemProvider/archive.zip) + +Content is licensed under the [Google BSD License](http://code.google.com/google_bsd_license.html). + +Calls +----- + +* [fileSystemProvider.mount](https://developer.chrome.com/extensions/fileSystemProvider#method-mount) +* [fileSystemProvider.onCloseFileRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onCloseFileRequested) +* [fileSystemProvider.onGetMetadataRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onGetMetadataRequested) +* [fileSystemProvider.onOpenFileRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onOpenFileRequested) +* [fileSystemProvider.onReadDirectoryRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onReadDirectoryRequested) +* [fileSystemProvider.onReadFileRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onReadFileRequested) +* [fileSystemProvider.onUnmountRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onUnmountRequested) +* [fileSystemProvider.unmount](https://developer.chrome.com/extensions/fileSystemProvider#method-unmount) +* [runtime.onStartup](https://developer.chrome.com/extensions/runtime#event-onStartup) +* [runtime.onSuspend](https://developer.chrome.com/extensions/runtime#event-onSuspend) +* [storage.StorageArea.get](https://developer.chrome.com/extensions/storage#method-StorageArea-get) +* [storage.StorageArea.set](https://developer.chrome.com/extensions/storage#method-StorageArea-set) \ No newline at end of file diff --git a/archive/background.js b/archive/background.js new file mode 100644 index 0000000..09458cd --- /dev/null +++ b/archive/background.js @@ -0,0 +1,247 @@ +// Copyright 2014 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. + +'use strict'; + +// Metadata is stored in files as serialized to JSON maps. See contents of +// example1.fake and example2.fake. + +// Multiple volumes can be opened at the same time. The key is the +// fileSystemId, which is the same as the file's displayPath. +// The value is a Volume object. +var volumes = {}; + +// Defines a volume object that contains information about a mounted file +// system. +function Volume(entry, metadata, opt_openedFiles) { + // Used for restoring the opened file entry after resuming the event page. + this.entry = entry; + + // The volume metadata. + this.metadata = []; + for (var path in metadata) { + this.metadata[path] = metadata[path]; + // Date object is serialized in JSON as string. + this.metadata[path].modificationTime = + new Date(metadata[path].modificationTime); + } + + // A map with currently opened files. The key is a requestId value from the + // openFileRequested event, and the value is the file path. + this.openedFiles = opt_openedFiles ? opt_openedFiles : {}; +}; + +function onUnmountRequested(options, onSuccess, onError) { + if (Object.keys(volumes[options.fileSystemId].openedFiles).length != 0) { + onError('IN_USE'); + return; + } + + chrome.fileSystemProvider.unmount( + {fileSystemId: options.fileSystemId}, + function() { + delete volumes[options.fileSystemId]; + saveState(); // Remove volume from local storage state. + onSuccess(); + }, + function() { + onError('FAILED'); + }); +}; + +function onGetMetadataRequested(options, onSuccess, onError) { + restoreState(options.fileSystemId, function () { + var entryMetadata = + volumes[options.fileSystemId].metadata[options.entryPath]; + if (!entryMetadata) + error('NOT_FOUND'); + else + onSuccess(entryMetadata); + }, onError); +}; + +function onReadDirectoryRequested(options, onSuccess, onError) { + restoreState(options.fileSystemId, function () { + var directoryMetadata = + volumes[options.fileSystemId].metadata[options.directoryPath]; + if (!directoryMetadata) { + onError('NOT_FOUND'); + return; + } + if (!directoryMetadata.isDirectory) { + onError('NOT_A_DIRECTORY'); + return; + } + + // Retrieve directory contents from metadata. + var entries = []; + for (var entry in volumes[options.fileSystemId].metadata) { + // Do not add itself on the list. + if (entry == options.directoryPath) + continue; + // Check if the entry is a child of the requested directory. + if (entry.indexOf(options.directoryPath) != 0) + continue; + // Restrict to direct children only. + if (entry.substring(options.directoryPath.length + 1).indexOf('/') != -1) + continue; + + entries.push(volumes[options.fileSystemId].metadata[entry]); + } + onSuccess(entries, false /* Last call. */); + }, onError); +}; + +function onOpenFileRequested(options, onSuccess, onError) { + restoreState(options.fileSystemId, function () { + if (options.mode != 'READ' || options.create) { + onError('INVALID_OPERATION'); + } else { + volumes[options.fileSystemId].openedFiles[options.requestId] = + options.filePath; + onSuccess(); + } + }, onError); +}; + +function onCloseFileRequested(options, onSuccess, onError) { + restoreState(options.fileSystemId, function () { + if (!volumes[options.fileSystemId].openedFiles[options.openRequestId]) { + onError('INVALID_OPERATION'); + } else { + delete volumes[options.fileSystemId].openedFiles[options.openRequestId]; + onSuccess(); + } + }, onError); +}; + +function onReadFileRequested(options, onSuccess, onError) { + restoreState(options.fileSystemId, function () { + var filePath = + volumes[options.fileSystemId].openedFiles[options.openRequestId]; + if (!filePath) { + onError('INVALID_OPERATION'); + return; + } + + var contents = volumes[options.fileSystemId].metadata[filePath].contents; + + // Write the contents as ASCII text. + var buffer = new ArrayBuffer(options.length); + var bufferView = new Uint8Array(buffer); + for (var i = 0; i < options.length; i++) { + bufferView[i] = contents.charCodeAt(i); + } + + onSuccess(buffer, false /* Last call. */); + }, onError); +}; + +// Saves state in case of restarts, event page suspend, crashes, etc. +function saveState() { + var state = {}; + for (var volumeId in volumes) { + var entryId = chrome.fileSystem.retainEntry(volumes[volumeId].entry); + state[volumeId] = { + entryId: entryId, + openedFiles: volumes[volumeId].openedFiles + }; + } + chrome.storage.local.set({state: state}); +} + +// Restores metadata for the passed file system ID. +function restoreState(fileSystemId, onSuccess, onError) { + chrome.storage.local.get(['state'], function(result) { + // Check if metadata for the given file system is alread in memory. + if (volumes[fileSystemId]) { + onSuccess(); + return; + } + + chrome.fileSystem.restoreEntry( + result.state[fileSystemId].entryId, + function(entry) { + readMetadataFromFile(entry, + function(metadata) { + volumes[fileSystemId] = new Volume(entry, metadata, + result.state[fileSystemId].openedFiles); + onSuccess(); + }, onError); + }); + }); +} + +// Reads metadata from a file and returns it with the onSuccess callback. +function readMetadataFromFile(entry, onSuccess, onError) { + entry.file(function(file) { + var fileReader = new FileReader(); + fileReader.onload = function(event) { + onSuccess(JSON.parse(event.target.result)); + }; + + fileReader.onerror = function(event) { + onError('FAILED'); + }; + + fileReader.readAsText(file); + }); +} + +// Event called on opening a file with the extension or mime type +// declared in the manifest file. +chrome.app.runtime.onLaunched.addListener(function(event) { + event.items.forEach(function(item) { + readMetadataFromFile(item.entry, + function(metadata) { + // Mount the volume and save its information in local storage + // in order to be able to recover the metadata in case of + // restarts, system crashes, etc. + chrome.fileSystem.getDisplayPath(item.entry, function(displayPath) { + volumes[displayPath] = new Volume(item.entry, metadata); + chrome.fileSystemProvider.mount( + {fileSystemId: displayPath, displayName: item.entry.name}, + function() { saveState(); }, + function() { console.error('Failed to mount.'); }); + }); + }, + function(error) { + console.error(error); + }); + }); +}); + +// Event called on a profile startup. +chrome.runtime.onStartup.addListener(function () { + chrome.storage.local.get(['state'], function(result) { + // Nothing to change. + if (!result.state) + return; + + // Remove files opened before the profile shutdown from the local storage. + for (var volumeId in result.state) { + result.state[volumeId].openedFiles = {}; + } + chrome.storage.local.set({state: result.state}); + }); +}); + +// Save the state before suspending the event page, so we can resume it +// once new events arrive. +chrome.runtime.onSuspend.addListener(function() { + saveState(); +}); + +chrome.fileSystemProvider.onUnmountRequested.addListener( + onUnmountRequested); +chrome.fileSystemProvider.onGetMetadataRequested.addListener( + onGetMetadataRequested); +chrome.fileSystemProvider.onReadDirectoryRequested.addListener( + onReadDirectoryRequested); +chrome.fileSystemProvider.onOpenFileRequested.addListener( + onOpenFileRequested); +chrome.fileSystemProvider.onCloseFileRequested.addListener( + onCloseFileRequested); +chrome.fileSystemProvider.onReadFileRequested.addListener( + onReadFileRequested); diff --git a/archive/example1.fake b/archive/example1.fake new file mode 100644 index 0000000..4bcb726 --- /dev/null +++ b/archive/example1.fake @@ -0,0 +1,34 @@ +{ + "/": { + "isDirectory": true, + "name": "/", + "size": 0, + "modificationTime": "2014-06-26T08:47:11.591Z" + }, + "/file1.txt": { + "isDirectory": false, + "name": "file1.txt", + "size": 46, + "modificationTime": "2014-06-26T08:47:11.591Z", + "contents": "It works!\nEverything gets displayed correctly." + }, + "/file2": { + "isDirectory": false, + "name": "file2", + "size": 150, + "modificationTime": "2014-06-26T08:47:11.591Z" + }, + "/dir": { + "isDirectory": true, + "name": "dir", + "size": 0, + "modificationTime": "2014-06-26T08:47:11.591Z" + }, + "/dir/file3.txt": { + "isDirectory": false, + "name": "file3.txt", + "size": 21, + "modificationTime": "2014-06-26T08:47:11.591Z", + "contents": "Just another example." + } +} diff --git a/archive/example2.fake b/archive/example2.fake new file mode 100644 index 0000000..a409b14 --- /dev/null +++ b/archive/example2.fake @@ -0,0 +1,15 @@ +{ + "/": { + "isDirectory": true, + "name": "/", + "size": 0, + "modificationTime": "2014-06-26T08:47:11.591Z" + }, + "/file.txt": { + "isDirectory": false, + "name": "file.txt", + "size": 9, + "modificationTime": "2014-06-26T08:47:11.591Z", + "contents": "It works!" + } +} diff --git a/archive/manifest.json b/archive/manifest.json new file mode 100644 index 0000000..0fd15bc --- /dev/null +++ b/archive/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "Fake Archive Handler App", + "version": "0.1", + "manifest_version": 2, + "description": "Demonstrate File System Provider API usage for apps.", + "permissions": [ + "fileSystemProvider", + {"fileSystem": ["retainEntries"]}, + "storage" + ], + "file_handlers": { + "fake": { + "types": ["application/fake"], + "extensions": ["fake"], + "title": "Open fake archive" + } + }, + "app": { + "background": { + "scripts": [ + "background.js" + ] + } + } +} diff --git a/background-simple/background.html b/background-simple/background.html index 12d906e..7095d3f 100644 --- a/background-simple/background.html +++ b/background-simple/background.html @@ -6,9 +6,8 @@ --> diff --git a/basic/README.md b/basic/README.md index 015f449..fed61f3 100644 --- a/basic/README.md +++ b/basic/README.md @@ -11,8 +11,8 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [bookmarks.create](http://developer.chrome.com/extensions/bookmarks.html#method-create) -* [bookmarks.getTree](http://developer.chrome.com/extensions/bookmarks.html#method-getTree) -* [bookmarks.remove](http://developer.chrome.com/extensions/bookmarks.html#method-remove) -* [bookmarks.update](http://developer.chrome.com/extensions/bookmarks.html#method-update) -* [tabs.create](http://developer.chrome.com/extensions/tabs.html#method-create) \ No newline at end of file +* [bookmarks.create](https://developer.chrome.com/extensions/bookmarks#method-create) +* [bookmarks.getTree](https://developer.chrome.com/extensions/bookmarks#method-getTree) +* [bookmarks.remove](https://developer.chrome.com/extensions/bookmarks#method-remove) +* [bookmarks.update](https://developer.chrome.com/extensions/bookmarks#method-update) +* [tabs.create](https://developer.chrome.com/extensions/tabs#method-create) \ No newline at end of file diff --git a/basic_1/README.md b/basic_1/README.md index 70a4106..5a291ca 100644 --- a/basic_1/README.md +++ b/basic_1/README.md @@ -11,4 +11,4 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [browsingData.remove](http://developer.chrome.com/extensions/browsingData.html#method-remove) \ No newline at end of file +* [browsingData.remove](https://developer.chrome.com/extensions/browsingData#method-remove) \ No newline at end of file diff --git a/basic_2/README.md b/basic_2/README.md index 18b786a..748f7e8 100644 --- a/basic_2/README.md +++ b/basic_2/README.md @@ -11,6 +11,5 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [contextMenus.create](http://developer.chrome.com/extensions/contextMenus.html#method-create) -* [extension.lastError](http://developer.chrome.com/extensions/extension.html#property-lastError) -* [extension.lastError.message](http://developer.chrome.com/extensions/extension.html#property-lastError-message) \ No newline at end of file +* [contextMenus.create](https://developer.chrome.com/extensions/contextMenus#method-create) +* [extension.lastError](https://developer.chrome.com/extensions/extension#property-lastError) \ No newline at end of file diff --git a/basic_3/README.md b/basic_3/README.md index e2b507e..13e0d72 100644 --- a/basic_3/README.md +++ b/basic_3/README.md @@ -11,5 +11,5 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [signedInDevices.get](http://developer.chrome.com/extensions/signedInDevices.html#method-get) -* [signedInDevices.onDeviceInfoChange](http://developer.chrome.com/extensions/signedInDevices.html#event-onDeviceInfoChange) \ No newline at end of file +* [signedInDevices.get](https://developer.chrome.com/extensions/signedInDevices#method-get) +* [signedInDevices.onDeviceInfoChange](https://developer.chrome.com/extensions/signedInDevices#event-onDeviceInfoChange) \ No newline at end of file diff --git a/basic_4/README.md b/basic_4/README.md index a394d7d..9470053 100644 --- a/basic_4/README.md +++ b/basic_4/README.md @@ -11,19 +11,19 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [alarms.create](http://developer.chrome.com/extensions/alarms.html#method-create) -* [alarms.onAlarm](http://developer.chrome.com/extensions/alarms.html#event-onAlarm) -* [bookmarks.onRemoved](http://developer.chrome.com/extensions/bookmarks.html#event-onRemoved) -* [browserAction.onClicked](http://developer.chrome.com/extensions/browserAction.html#event-onClicked) -* [browserAction.setBadgeText](http://developer.chrome.com/extensions/browserAction.html#method-setBadgeText) -* [commands.onCommand](http://developer.chrome.com/extensions/commands.html#event-onCommand) -* [declarativeWebRequest.RedirectRequest](http://developer.chrome.com/extensions/declarativeWebRequest.html#type-RedirectRequest) -* [declarativeWebRequest.RequestMatcher](http://developer.chrome.com/extensions/declarativeWebRequest.html#type-RequestMatcher) -* [runtime.onInstalled](http://developer.chrome.com/extensions/runtime.html#event-onInstalled) -* [runtime.onMessage](http://developer.chrome.com/extensions/runtime.html#event-onMessage) -* [runtime.onSuspend](http://developer.chrome.com/extensions/runtime.html#event-onSuspend) -* [runtime.sendMessage](http://developer.chrome.com/extensions/runtime.html#method-sendMessage) -* [tabs.create](http://developer.chrome.com/extensions/tabs.html#method-create) -* [tabs.executeScript](http://developer.chrome.com/extensions/tabs.html#method-executeScript) -* [tabs.query](http://developer.chrome.com/extensions/tabs.html#method-query) -* [tabs.sendMessage](http://developer.chrome.com/extensions/tabs.html#method-sendMessage) \ No newline at end of file +* [alarms.create](https://developer.chrome.com/extensions/alarms#method-create) +* [alarms.onAlarm](https://developer.chrome.com/extensions/alarms#event-onAlarm) +* [bookmarks.onRemoved](https://developer.chrome.com/extensions/bookmarks#event-onRemoved) +* [browserAction.onClicked](https://developer.chrome.com/extensions/browserAction#event-onClicked) +* [browserAction.setBadgeText](https://developer.chrome.com/extensions/browserAction#method-setBadgeText) +* [commands.onCommand](https://developer.chrome.com/extensions/commands#event-onCommand) +* [declarativeWebRequest.RedirectRequest](https://developer.chrome.com/extensions/declarativeWebRequest#type-RedirectRequest) +* [declarativeWebRequest.RequestMatcher](https://developer.chrome.com/extensions/declarativeWebRequest#type-RequestMatcher) +* [runtime.onInstalled](https://developer.chrome.com/extensions/runtime#event-onInstalled) +* [runtime.onMessage](https://developer.chrome.com/extensions/runtime#event-onMessage) +* [runtime.onSuspend](https://developer.chrome.com/extensions/runtime#event-onSuspend) +* [runtime.sendMessage](https://developer.chrome.com/extensions/runtime#method-sendMessage) +* [tabs.create](https://developer.chrome.com/extensions/tabs#method-create) +* [tabs.executeScript](https://developer.chrome.com/extensions/tabs#method-executeScript) +* [tabs.query](https://developer.chrome.com/extensions/tabs#method-query) +* [tabs.sendMessage](https://developer.chrome.com/extensions/tabs#method-sendMessage) \ No newline at end of file diff --git a/basic_5/README.md b/basic_5/README.md index f13ee39..9a36770 100644 --- a/basic_5/README.md +++ b/basic_5/README.md @@ -1,20 +1,20 @@ -Test IME +File System Provider API Extension Example ======= -A simple IME that converts all keystrokes to upper case. +Demonstrate features of the API like mounting, listing directories, etc for extensions. -[Zipfile](http://developer.chrome.com/extensions/examples/api/input.ime/basic.zip) +[Zipfile](http://developer.chrome.com/extensions/examples/api/fileSystemProvider/basic.zip) Content is licensed under the [Google BSD License](http://code.google.com/google_bsd_license.html). Calls ----- -* [input.ime](http://developer.chrome.com/extensions/input.ime.html) -* [input.ime.commitText](http://developer.chrome.com/extensions/input.ime.html#method-commitText) -* [input.ime.onActivate](http://developer.chrome.com/extensions/input.ime.html#event-onActivate) -* [input.ime.onBlur](http://developer.chrome.com/extensions/input.ime.html#event-onBlur) -* [input.ime.onDeactivated](http://developer.chrome.com/extensions/input.ime.html#event-onDeactivated) -* [input.ime.onFocus](http://developer.chrome.com/extensions/input.ime.html#event-onFocus) -* [input.ime.onKeyEvent](http://developer.chrome.com/extensions/input.ime.html#event-onKeyEvent) \ No newline at end of file +* [fileSystemProvider.mount](https://developer.chrome.com/extensions/fileSystemProvider#method-mount) +* [fileSystemProvider.onCloseFileRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onCloseFileRequested) +* [fileSystemProvider.onGetMetadataRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onGetMetadataRequested) +* [fileSystemProvider.onOpenFileRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onOpenFileRequested) +* [fileSystemProvider.onReadDirectoryRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onReadDirectoryRequested) +* [fileSystemProvider.onReadFileRequested](https://developer.chrome.com/extensions/fileSystemProvider#event-onReadFileRequested) +* [runtime.onInstalled](https://developer.chrome.com/extensions/runtime#event-onInstalled) \ No newline at end of file diff --git a/basic_5/background.js b/basic_5/background.js new file mode 100644 index 0000000..f95a098 --- /dev/null +++ b/basic_5/background.js @@ -0,0 +1,119 @@ +// Copyright 2014 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. + +'use strict'; + +// Fake data similar to a file system structure. +var MODIFICATION_DATE = new Date(); +var SHORT_CONTENTS = 'Just another example.'; +var LONGER_CONTENTS = 'It works!\nEverything gets displayed correctly.'; + +var METADATA = { + '/': {isDirectory: true, name: '/', size: 0, + modificationTime: MODIFICATION_DATE}, + '/file1.txt': {isDirectory: false, name: 'file1.txt', + size: LONGER_CONTENTS.length, modificationTime: MODIFICATION_DATE, + contents: LONGER_CONTENTS}, + '/file2': {isDirectory: false, name: 'file2', size: 150, + modificationTime: MODIFICATION_DATE}, + '/dir': {isDirectory: true, name: 'dir', size: 0, + modificationTime: MODIFICATION_DATE}, + '/dir/file3.txt': {isDirectory: false, name: 'file3.txt', + size: SHORT_CONTENTS.length, modificationTime: MODIFICATION_DATE, + contents: SHORT_CONTENTS}}; + +// A map with currently opened files. As key it has requestId of +// openFileRequested and as a value the file path. +var openedFiles = {}; + +function onGetMetadataRequested(options, onSuccess, onError) { + if (!METADATA[options.entryPath]) + onError('NOT_FOUND'); + else + onSuccess(METADATA[options.entryPath]); +} + +function onReadDirectoryRequested(options, onSuccess, onError) { + if (!METADATA[options.directoryPath]) { + onError('NOT_FOUND'); + return; + } + if (!METADATA[options.directoryPath].isDirectory) { + onError('NOT_A_DIRECTORY'); + return; + } + + // Retrieve directory contents from METADATA. + var entries = []; + for (var entry in METADATA) { + // Do not add itself on the list. + if (entry == options.directoryPath) + continue; + // Check if the entry is a child of the requested directory. + if (entry.indexOf(options.directoryPath) != 0) + continue; + // Restrict to direct children only. + if (entry.substring(options.directoryPath.length + 1).indexOf('/') != -1) + continue; + + entries.push(METADATA[entry]); + } + onSuccess(entries, false /* Last call. */); +} + +function onOpenFileRequested(options, onSuccess, onError) { + if (options.mode != 'READ' || options.create) { + onError('INVALID_OPERATION'); + } else { + openedFiles[options.requestId] = options.filePath; + onSuccess(); + } +} + +function onCloseFileRequested(options, onSuccess, onError) { + if (!openedFiles[options.openRequestId]) { + onError('INVALID_OPERATION'); + } else { + delete openedFiles[options.openRequestId]; + onSuccess(); + } +} + +function onReadFileRequested(options, onSuccess, onError) { + if (!openedFiles[options.openRequestId]) { + onError('INVALID_OPERATION'); + return; + } + + var contents = + METADATA[openedFiles[options.openRequestId]].contents; + + // Write the contents as ASCII text. + var buffer = new ArrayBuffer(options.length); + var bufferView = new Uint8Array(buffer); + for (var i = 0; i < options.length; i++) { + bufferView[i] = contents.charCodeAt(i); + } + + onSuccess(buffer, false /* Last call. */); +} + +// Mount the file system. +chrome.runtime.onInstalled.addListener(function(details) { + chrome.fileSystemProvider.mount( + {fileSystemId: 'sample-file-system', displayName: 'Sample File System'}, + function() {}, + function() { console.error('Failed to mount.'); }); +}); + +chrome.fileSystemProvider.onGetMetadataRequested.addListener( + onGetMetadataRequested); +chrome.fileSystemProvider.onReadDirectoryRequested.addListener( + onReadDirectoryRequested); +chrome.fileSystemProvider.onOpenFileRequested.addListener( + onOpenFileRequested); +chrome.fileSystemProvider.onCloseFileRequested.addListener( + onCloseFileRequested); +chrome.fileSystemProvider.onReadFileRequested.addListener( + onReadFileRequested); diff --git a/basic_5/manifest.json b/basic_5/manifest.json index e21aec1..c480355 100644 --- a/basic_5/manifest.json +++ b/basic_5/manifest.json @@ -1,22 +1,15 @@ { - "name": "Test IME", - "version": "1.0", + "name": "File System Provider API Extension Example", + "version": "0.1", "manifest_version": 2, - "description": "A simple IME that converts all keystrokes to upper case.", - "background": { - "scripts": ["main.js"] - }, + "description": + "Demonstrate features of the API like mounting, listing directories, etc for extensions.", "permissions": [ - "input" + "fileSystemProvider" ], - "input_components": [ - { - "name": "Test IME", - "type": "ime", - "id": "test", - "description": "Test IME", // A user visible description - "language": "en-US", // The primary language this IME is used for - "layouts": ["us::eng"] // The supported keyboard layouts for this IME - } - ] + "background": { + "scripts": [ + "background.js" + ] + } } diff --git a/basic_6/README.md b/basic_6/README.md index d6d8e4d..d387f95 100644 --- a/basic_6/README.md +++ b/basic_6/README.md @@ -1,15 +1,20 @@ -Top Sites +Test IME ======= -Shows the top sites in a browser action +A simple IME that converts all keystrokes to upper case. -[Zipfile](http://developer.chrome.com/extensions/examples/api/topsites/basic.zip) +[Zipfile](http://developer.chrome.com/extensions/examples/api/input.ime/basic.zip) Content is licensed under the [Google BSD License](http://code.google.com/google_bsd_license.html). Calls ----- -* [tabs.create](http://developer.chrome.com/extensions/tabs.html#method-create) -* [topSites.get](http://developer.chrome.com/extensions/topSites.html#method-get) \ No newline at end of file +* [input.ime](https://developer.chrome.com/extensions/input.ime) +* [input.ime.commitText](https://developer.chrome.com/extensions/input.ime#method-commitText) +* [input.ime.onActivate](https://developer.chrome.com/extensions/input.ime#event-onActivate) +* [input.ime.onBlur](https://developer.chrome.com/extensions/input.ime#event-onBlur) +* [input.ime.onDeactivated](https://developer.chrome.com/extensions/input.ime#event-onDeactivated) +* [input.ime.onFocus](https://developer.chrome.com/extensions/input.ime#event-onFocus) +* [input.ime.onKeyEvent](https://developer.chrome.com/extensions/input.ime#event-onKeyEvent) \ No newline at end of file diff --git a/basic_6/main.js b/basic_6/main.js new file mode 100644 index 0000000..fdff7ce --- /dev/null +++ b/basic_6/main.js @@ -0,0 +1,37 @@ +// Copyright 2013 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. + +var ime_api = chrome.input.ime; + +var context_id = -1; + +console.log("Initializing IME"); + +ime_api.onFocus.addListener(function(context) { + console.log('onFocus:' + context.contextID); + context_id = context.contextID; +}); +ime_api.onBlur.addListener(function(contextID) { + console.log('onBlur:' + contextID); + context_id = -1; +}); + +ime_api.onActivate.addListener(function(engineID) { + console.log('onActivate:' + engineID); +}); +ime_api.onDeactivated.addListener(function(engineID) { + console.log('onDeactivated:' + engineID); +}); + +ime_api.onKeyEvent.addListener( +function(engineID, keyData) { + console.log('onKeyEvent:' + keyData.key + " context: " + context_id); + if (keyData.type == "keydown" && keyData.key.match(/^[a-z]$/)) { + chrome.input.ime.commitText({"contextID": context_id, + "text": keyData.key.toUpperCase()}); + return true; + } + + return false +}); diff --git a/basic_6/manifest.json b/basic_6/manifest.json index c042c09..e21aec1 100644 --- a/basic_6/manifest.json +++ b/basic_6/manifest.json @@ -1,11 +1,22 @@ { - "name": "Top Sites", - "version": "1.2", - "description": "Shows the top sites in a browser action", - "permissions": ["topSites"], - "browser_action": { - "default_icon": "icon.png", - "default_popup": "popup.html" + "name": "Test IME", + "version": "1.0", + "manifest_version": 2, + "description": "A simple IME that converts all keystrokes to upper case.", + "background": { + "scripts": ["main.js"] }, - "manifest_version": 2 + "permissions": [ + "input" + ], + "input_components": [ + { + "name": "Test IME", + "type": "ime", + "id": "test", + "description": "Test IME", // A user visible description + "language": "en-US", // The primary language this IME is used for + "layouts": ["us::eng"] // The supported keyboard layouts for this IME + } + ] } diff --git a/basic_7/README.md b/basic_7/README.md index 0f0f269..4ce03e7 100644 --- a/basic_7/README.md +++ b/basic_7/README.md @@ -1,26 +1,15 @@ -WebNavigation Tech Demo +Top Sites ======= -Demonstration of the WebNavigation extension API. +Shows the top sites in a browser action -[Zipfile](http://developer.chrome.com/extensions/examples/api/webNavigation/basic.zip) +[Zipfile](http://developer.chrome.com/extensions/examples/api/topsites/basic.zip) Content is licensed under the [Google BSD License](http://code.google.com/google_bsd_license.html). Calls ----- -* [extension.onRequest](http://developer.chrome.com/extensions/extension.html#event-onRequest) -* [extension.sendRequest](http://developer.chrome.com/extensions/extension.html#method-sendRequest) -* [i18n.getMessage](http://developer.chrome.com/extensions/i18n.html#method-getMessage) -* [runtime.onStartup](http://developer.chrome.com/extensions/runtime.html#event-onStartup) -* [storage.StorageArea.get](http://developer.chrome.com/extensions/storage.html#method-StorageArea-get) -* [storage.StorageArea.set](http://developer.chrome.com/extensions/storage.html#method-StorageArea-set) -* [webNavigation.onBeforeNavigate](http://developer.chrome.com/extensions/webNavigation.html#event-onBeforeNavigate) -* [webNavigation.onCommitted](http://developer.chrome.com/extensions/webNavigation.html#event-onCommitted) -* [webNavigation.onCompleted](http://developer.chrome.com/extensions/webNavigation.html#event-onCompleted) -* [webNavigation.onCreatedNavigationTarget](http://developer.chrome.com/extensions/webNavigation.html#event-onCreatedNavigationTarget) -* [webNavigation.onErrorOccurred](http://developer.chrome.com/extensions/webNavigation.html#event-onErrorOccurred) -* [webNavigation.onHistoryStateUpdated](http://developer.chrome.com/extensions/webNavigation.html#event-onHistoryStateUpdated) -* [webNavigation.onReferenceFragmentUpdated](http://developer.chrome.com/extensions/webNavigation.html#event-onReferenceFragmentUpdated) \ No newline at end of file +* [tabs.create](https://developer.chrome.com/extensions/tabs#method-create) +* [topSites.get](https://developer.chrome.com/extensions/topSites#method-get) \ No newline at end of file diff --git a/basic_7/icon.png b/basic_7/icon.png index 103ff36..0421e4c 100644 Binary files a/basic_7/icon.png and b/basic_7/icon.png differ diff --git a/basic_7/manifest.json b/basic_7/manifest.json index 2c0d435..c042c09 100644 --- a/basic_7/manifest.json +++ b/basic_7/manifest.json @@ -1,18 +1,11 @@ { - "name": "__MSG_extName__", - "version": "0.2", - "description": "__MSG_extDescription__", - "default_locale": "en", - "background": { - "persistent": false, - "scripts": ["navigation_collector.js", "background.js"] - }, + "name": "Top Sites", + "version": "1.2", + "description": "Shows the top sites in a browser action", + "permissions": ["topSites"], "browser_action": { "default_icon": "icon.png", "default_popup": "popup.html" }, - "permissions": [ - "webNavigation", "storage" - ], "manifest_version": 2 } diff --git a/basic_7/popup.html b/basic_7/popup.html index 1f51802..1247307 100644 --- a/basic_7/popup.html +++ b/basic_7/popup.html @@ -1,17 +1,8 @@ - - + - - WebNavigation Tech Demo Popup - - -

Most Requested URLs

-
- +

Most Visited:

+
+ diff --git a/basic_7/popup.js b/basic_7/popup.js index 35f6e00..5f6761d 100644 --- a/basic_7/popup.js +++ b/basic_7/popup.js @@ -1,36 +1,27 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright 2013 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. -/** - * @filedescription Initializes the extension's popup page. - */ +// Event listener for clicks on links in a browser action popup. +// Open the link in a new tab of the current window. +function onAnchorClick(event) { + chrome.tabs.create({ url: event.srcElement.href }); + return false; +} -chrome.extension.sendRequest( - {'type': 'getMostRequestedUrls'}, - function generateList(response) { - var section = document.querySelector('body>section'); - var results = response.result; - var ol = document.createElement('ol'); - var li, p, em, code, text; - var i; - for (i = 0; i < results.length; i++ ) { - li = document.createElement('li'); - p = document.createElement('p'); - em = document.createElement('em'); - em.textContent = i + 1; - code = document.createElement('code'); - code.textContent = results[i].url; - text = document.createTextNode( - chrome.i18n.getMessage('navigationDescription', - [results[i].numRequests, - results[i].average])); - p.appendChild(em); - p.appendChild(code); - p.appendChild(text); - li.appendChild(p); - ol.appendChild(li); - } - section.innerHTML = ''; - section.appendChild(ol); - }); +// Given an array of URLs, build a DOM list of these URLs in the +// browser action popup. +function buildPopupDom(mostVisitedURLs) { + var popupDiv = document.getElementById('mostVisited_div'); + var ol = popupDiv.appendChild(document.createElement('ol')); + + for (var i = 0; i < mostVisitedURLs.length; i++) { + var li = ol.appendChild(document.createElement('li')); + var a = li.appendChild(document.createElement('a')); + a.href = mostVisitedURLs[i].url; + a.appendChild(document.createTextNode(mostVisitedURLs[i].title)); + a.addEventListener('click', onAnchorClick); + } +} + +chrome.topSites.get(buildPopupDom); diff --git a/basic_8/README.md b/basic_8/README.md new file mode 100644 index 0000000..a1887af --- /dev/null +++ b/basic_8/README.md @@ -0,0 +1,26 @@ + +WebNavigation Tech Demo +======= + +Demonstration of the WebNavigation extension API. + +[Zipfile](http://developer.chrome.com/extensions/examples/api/webNavigation/basic.zip) + +Content is licensed under the [Google BSD License](http://code.google.com/google_bsd_license.html). + +Calls +----- + +* [i18n.getMessage](https://developer.chrome.com/extensions/i18n#method-getMessage) +* [runtime.onMessage](https://developer.chrome.com/extensions/runtime#event-onMessage) +* [runtime.onStartup](https://developer.chrome.com/extensions/runtime#event-onStartup) +* [runtime.sendMessage](https://developer.chrome.com/extensions/runtime#method-sendMessage) +* [storage.StorageArea.get](https://developer.chrome.com/extensions/storage#method-StorageArea-get) +* [storage.StorageArea.set](https://developer.chrome.com/extensions/storage#method-StorageArea-set) +* [webNavigation.onBeforeNavigate](https://developer.chrome.com/extensions/webNavigation#event-onBeforeNavigate) +* [webNavigation.onCommitted](https://developer.chrome.com/extensions/webNavigation#event-onCommitted) +* [webNavigation.onCompleted](https://developer.chrome.com/extensions/webNavigation#event-onCompleted) +* [webNavigation.onCreatedNavigationTarget](https://developer.chrome.com/extensions/webNavigation#event-onCreatedNavigationTarget) +* [webNavigation.onErrorOccurred](https://developer.chrome.com/extensions/webNavigation#event-onErrorOccurred) +* [webNavigation.onHistoryStateUpdated](https://developer.chrome.com/extensions/webNavigation#event-onHistoryStateUpdated) +* [webNavigation.onReferenceFragmentUpdated](https://developer.chrome.com/extensions/webNavigation#event-onReferenceFragmentUpdated) \ No newline at end of file diff --git a/basic_8/_locales/en/messages.json b/basic_8/_locales/en/messages.json new file mode 100644 index 0000000..e639880 --- /dev/null +++ b/basic_8/_locales/en/messages.json @@ -0,0 +1,52 @@ +{ + "extName": { + "message": "WebNavigation Tech Demo", + "description": "The extension name." + }, + "extDescription": { + "message": "Demonstration of the WebNavigation extension API.", + "description": "The extension description." + }, + + "navigationDescription": { + "message": ", requested $NUM$ times. Loaded in an average of $LOAD$ miliseconds.", + "description": "The message posted in the popup for each stored navigation.", + "placeholders": { + "NUM": { + "content": "$1", + "example": "4 (The number of times this URL was accessed.)" + }, + "LOAD": { + "content": "$2", + "example": "12.345 (The average load time in miliseconds.)" + } + } + }, + + "inHandler": { + "message": "In webNavigation[`%s`] handler: %o", + "description": "Notification displayed for each webNavigation event." + }, + + "inHandlerError": { + "message": "In webNavigation[`%s`] handler: No data!", + "description": "Notification displayed in a webNavigation event handler without data!" + }, + + "errorCommittedWithoutPending": { + "message": "Wha? `onCommitted` for `%s` called, though it's not pending: %o", + "description": "Error logged when `onCommitted` is triggered on a non-pending request." + }, + "errorCompletedWithoutPending": { + "message": "Wha? `onCompleted` for `%s` called, though it's not pending: %o", + "description": "Error logged when `onCompleted` is triggered on a non-pending request." + }, + "errorErrorOccurredWithoutPending": { + "message": "Wha? `onErrorOccurred` for `%s` called, though it's not pending: %o", + "description": "Error logged when `onErrorOccurred` is triggered on a non-pending request." + }, + "errorCommittedWithoutPending": { + "message": "Wha? `onCompleted` for `%s` called, though it's not pending: %o", + "description": "Error logged when `onCompleted` is triggered on a non-pending request." + } +} diff --git a/basic_8/background.js b/basic_8/background.js new file mode 100644 index 0000000..a056515 --- /dev/null +++ b/basic_8/background.js @@ -0,0 +1,29 @@ +// 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. + +/** + * @filedescription Initializes the extension's background page. + */ + +var nav = new NavigationCollector(); + +var eventList = ['onBeforeNavigate', 'onCreatedNavigationTarget', + 'onCommitted', 'onCompleted', 'onDOMContentLoaded', + 'onErrorOccurred', 'onReferenceFragmentUpdated', 'onTabReplaced', + 'onHistoryStateUpdated']; + +eventList.forEach(function(e) { + chrome.webNavigation[e].addListener(function(data) { + if (typeof data) + console.log(chrome.i18n.getMessage('inHandler'), e, data); + else + console.error(chrome.i18n.getMessage('inHandlerError'), e); + }); +}); + +// Reset the navigation state on startup. We only want to collect data within a +// session. +chrome.runtime.onStartup.addListener(function() { + nav.resetDataStorage(); +}); diff --git a/basic_8/icon.png b/basic_8/icon.png new file mode 100644 index 0000000..103ff36 Binary files /dev/null and b/basic_8/icon.png differ diff --git a/basic_8/manifest.json b/basic_8/manifest.json new file mode 100644 index 0000000..2c0d435 --- /dev/null +++ b/basic_8/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "__MSG_extName__", + "version": "0.2", + "description": "__MSG_extDescription__", + "default_locale": "en", + "background": { + "persistent": false, + "scripts": ["navigation_collector.js", "background.js"] + }, + "browser_action": { + "default_icon": "icon.png", + "default_popup": "popup.html" + }, + "permissions": [ + "webNavigation", "storage" + ], + "manifest_version": 2 +} diff --git a/basic_8/navigation_collector.js b/basic_8/navigation_collector.js new file mode 100644 index 0000000..cec4db2 --- /dev/null +++ b/basic_8/navigation_collector.js @@ -0,0 +1,500 @@ +// 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. + +/** + * Implements the NavigationCollector object that powers the extension. + * + * @author mkwst@google.com (Mike West) + */ + +/** + * Collects navigation events, and provides a list of successful requests + * that you can do interesting things with. Calling the constructor will + * automatically bind handlers to the relevant webnavigation API events, + * and to a `getMostRequestedUrls` extension message for internal + * communication between background pages and popups. + * + * @constructor + */ +function NavigationCollector() { + /** + * A list of currently pending requests, implemented as a hash of each + * request's tab ID, frame ID, and URL in order to ensure uniqueness. + * + * @type {Object.} + * @private + */ + this.pending_ = {}; + + /** + * A list of completed requests, implemented as a hash of each + * request's tab ID, frame ID, and URL in order to ensure uniqueness. + * + * @type {Object.>} + * @private + */ + this.completed_ = {}; + + /** + * A list of requests that errored off, implemented as a hash of each + * request's tab ID, frame ID, and URL in order to ensure uniqueness. + * + * @type {Object.>} + * @private + */ + this.errored_ = {}; + + // Bind handlers to the 'webNavigation' events that we're interested + // in handling in order to build up a complete picture of the whole + // navigation event. + chrome.webNavigation.onCreatedNavigationTarget.addListener( + this.onCreatedNavigationTargetListener_.bind(this)); + chrome.webNavigation.onBeforeNavigate.addListener( + this.onBeforeNavigateListener_.bind(this)); + chrome.webNavigation.onCompleted.addListener( + this.onCompletedListener_.bind(this)); + chrome.webNavigation.onCommitted.addListener( + this.onCommittedListener_.bind(this)); + chrome.webNavigation.onErrorOccurred.addListener( + this.onErrorOccurredListener_.bind(this)); + chrome.webNavigation.onReferenceFragmentUpdated.addListener( + this.onReferenceFragmentUpdatedListener_.bind(this)); + chrome.webNavigation.onHistoryStateUpdated.addListener( + this.onHistoryStateUpdatedListener_.bind(this)); + + // Bind handler to extension messages for communication from popup. + chrome.runtime.onMessage.addListener(this.onMessageListener_.bind(this)); + + this.loadDataStorage_(); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * The possible transition types that explain how the navigation event + * was generated (i.e. "The user clicked on a link." or "The user submitted + * a form"). + * + * @see http://code.google.com/chrome/extensions/trunk/history.html + * @enum {string} + */ +NavigationCollector.NavigationType = { + AUTO_BOOKMARK: 'auto_bookmark', + AUTO_SUBFRAME: 'auto_subframe', + FORM_SUBMIT: 'form_submit', + GENERATED: 'generated', + KEYWORD: 'keyword', + KEYWORD_GENERATED: 'keyword_generated', + LINK: 'link', + MANUAL_SUBFRAME: 'manual_subframe', + RELOAD: 'reload', + START_PAGE: 'start_page', + TYPED: 'typed' +}; + +/** + * The possible transition qualifiers: + * + * * CLIENT_REDIRECT: Redirects caused by JavaScript, or a refresh meta tag + * on a page. + * + * * SERVER_REDIRECT: Redirected by the server via a 301/302 response. + * + * * FORWARD_BACK: User used the forward or back buttons to navigate through + * her browsing history. + * + * @enum {string} + */ +NavigationCollector.NavigationQualifier = { + CLIENT_REDIRECT: 'client_redirect', + FORWARD_BACK: 'forward_back', + SERVER_REDIRECT: 'server_redirect' +}; + +/** + * @typedef {{url: string, transitionType: NavigationCollector.NavigationType, + * transitionQualifier: Array., + * openedInNewTab: boolean, source: {frameId: ?number, tabId: ?number}, + * duration: number}} + */ +NavigationCollector.Request; + +/////////////////////////////////////////////////////////////////////////////// + +NavigationCollector.prototype = { + /** + * Returns a somewhat unique ID for a given WebNavigation request. + * + * @param {!{tabId: ?number, frameId: ?number}} data Information + * about the navigation event we'd like an ID for. + * @return {!string} ID created by combining the source tab ID and frame ID + * (or target tab/frame IDs if there's no source), as the API ensures + * that these will be unique across a single navigation event. + * @private + */ + parseId_: function(data) { + return data.tabId + '-' + (data.frameId ? data.frameId : 0); + }, + + + /** + * Creates an empty entry in the pending array if one doesn't already exist, + * and prepopulates the errored and completed arrays for ease of insertion + * later. + * + * @param {!string} id The request's ID, as produced by parseId_. + * @param {!string} url The request's URL. + */ + prepareDataStorage_: function(id, url) { + this.pending_[id] = this.pending_[id] || { + openedInNewTab: false, + source: { + frameId: null, + tabId: null + }, + start: null, + transitionQualifiers: [], + transitionType: null + }; + this.completed_[url] = this.completed_[url] || []; + this.errored_[url] = this.errored_[url] || []; + }, + + + /** + * Retrieves our saved data from storage. + * @private + */ + loadDataStorage_: function() { + chrome.storage.local.get({ + "completed": {}, + "errored": {}, + }, function(storage) { + this.completed_ = storage.completed; + this.errored_ = storage.errored; + }.bind(this)); + }, + + + /** + * Persists our state to the storage API. + * @private + */ + saveDataStorage_: function() { + chrome.storage.local.set({ + "completed": this.completed_, + "errored": this.errored_, + }); + }, + + + /** + * Resets our saved state to empty. + */ + resetDataStorage: function() { + this.completed_ = {}; + this.errored_ = {}; + this.saveDataStorage_(); + // Load again, in case there is an outstanding storage.get request. This + // one will reload the newly-cleared data. + this.loadDataStorage_(); + }, + + + /** + * Handler for the 'onCreatedNavigationTarget' event. Updates the + * pending request with a source frame/tab, and notes that it was opened in a + * new tab. + * + * Pushes the request onto the + * 'pending_' object, and stores it for later use. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onCreatedNavigationTargetListener_: function(data) { + var id = this.parseId_(data); + this.prepareDataStorage_(id, data.url); + this.pending_[id].openedInNewTab = data.tabId; + this.pending_[id].source = { + tabId: data.sourceTabId, + frameId: data.sourceFrameId + }; + this.pending_[id].start = data.timeStamp; + }, + + + /** + * Handler for the 'onBeforeNavigate' event. Pushes the request onto the + * 'pending_' object, and stores it for later use. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onBeforeNavigateListener_: function(data) { + var id = this.parseId_(data); + this.prepareDataStorage_(id, data.url); + this.pending_[id].start = this.pending_[id].start || data.timeStamp; + }, + + + /** + * Handler for the 'onCommitted' event. Updates the pending request with + * transition information. + * + * Pushes the request onto the + * 'pending_' object, and stores it for later use. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onCommittedListener_: function(data) { + var id = this.parseId_(data); + if (!this.pending_[id]) { + console.warn( + chrome.i18n.getMessage('errorCommittedWithoutPending'), + data.url, + data); + } else { + this.prepareDataStorage_(id, data.url); + this.pending_[id].transitionType = data.transitionType; + this.pending_[id].transitionQualifiers = + data.transitionQualifiers; + } + }, + + + /** + * Handler for the 'onReferenceFragmentUpdated' event. Updates the pending + * request with transition information. + * + * Pushes the request onto the + * 'pending_' object, and stores it for later use. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onReferenceFragmentUpdatedListener_: function(data) { + var id = this.parseId_(data); + if (!this.pending_[id]) { + this.completed_[data.url] = this.completed_[data.url] || []; + this.completed_[data.url].push({ + duration: 0, + openedInNewWindow: false, + source: { + frameId: null, + tabId: null + }, + transitionQualifiers: data.transitionQualifiers, + transitionType: data.transitionType, + url: data.url + }); + this.saveDataStorage_(); + } else { + this.prepareDataStorage_(id, data.url); + this.pending_[id].transitionType = data.transitionType; + this.pending_[id].transitionQualifiers = + data.transitionQualifiers; + } + }, + + + /** + * Handler for the 'onHistoryStateUpdated' event. Updates the pending + * request with transition information. + * + * Pushes the request onto the + * 'pending_' object, and stores it for later use. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onHistoryStateUpdatedListener_: function(data) { + var id = this.parseId_(data); + if (!this.pending_[id]) { + this.completed_[data.url] = this.completed_[data.url] || []; + this.completed_[data.url].push({ + duration: 0, + openedInNewWindow: false, + source: { + frameId: null, + tabId: null + }, + transitionQualifiers: data.transitionQualifiers, + transitionType: data.transitionType, + url: data.url + }); + this.saveDataStorage_(); + } else { + this.prepareDataStorage_(id, data.url); + this.pending_[id].transitionType = data.transitionType; + this.pending_[id].transitionQualifiers = + data.transitionQualifiers; + } + }, + + + /** + * Handler for the 'onCompleted` event. Pulls the request's data from the + * 'pending_' object, combines it with the completed event's data, and pushes + * a new NavigationCollector.Request object onto 'completed_'. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onCompletedListener_: function(data) { + var id = this.parseId_(data); + if (!this.pending_[id]) { + console.warn( + chrome.i18n.getMessage('errorCompletedWithoutPending'), + data.url, + data); + } else { + this.completed_[data.url].push({ + duration: (data.timeStamp - this.pending_[id].start), + openedInNewWindow: this.pending_[id].openedInNewWindow, + source: this.pending_[id].source, + transitionQualifiers: this.pending_[id].transitionQualifiers, + transitionType: this.pending_[id].transitionType, + url: data.url + }); + delete this.pending_[id]; + this.saveDataStorage_(); + } + }, + + + /** + * Handler for the 'onErrorOccurred` event. Pulls the request's data from the + * 'pending_' object, combines it with the completed event's data, and pushes + * a new NavigationCollector.Request object onto 'errored_'. + * + * @param {!Object} data The event data generated for this request. + * @private + */ + onErrorOccurredListener_: function(data) { + var id = this.parseId_(data); + if (!this.pending_[id]) { + console.error( + chrome.i18n.getMessage('errorErrorOccurredWithoutPending'), + data.url, + data); + } else { + this.prepareDataStorage_(id, data.url); + this.errored_[data.url].push({ + duration: (data.timeStamp - this.pending_[id].start), + openedInNewWindow: this.pending_[id].openedInNewWindow, + source: this.pending_[id].source, + transitionQualifiers: this.pending_[id].transitionQualifiers, + transitionType: this.pending_[id].transitionType, + url: data.url + }); + delete this.pending_[id]; + this.saveDataStorage_(); + } + }, + + /** + * Handle messages from the popup. + * + * @param {!{type:string}} message The external message to answer. + * @param {!MessageSender} sender Info about the script context that sent + * the message. + * @param {!function} sendResponse Function to call to send a response. + * @private + */ + onMessageListener_: function(message, sender, sendResponse) { + if (message.type === 'getMostRequestedUrls') + sendResponse({result: this.getMostRequestedUrls(message.num)}); + else + sendResponse({}); + }, + +/////////////////////////////////////////////////////////////////////////////// + + /** + * @return {Object.} The complete list of + * successful navigation requests. + */ + get completed() { + return this.completed_; + }, + + + /** + * @return {Object.} The complete list of + * unsuccessful navigation requests. + */ + get errored() { + return this.errored_; + }, + + + /** + * Get a list of the X most requested URLs. + * + * @param {number=} num The number of successful navigation requests to + * return. If 0 is passed in, or the argument left off entirely, all + * successful requests are returned. + * @return {Object.} The list of + * successful navigation requests, sorted in decending order of frequency. + */ + getMostRequestedUrls: function(num) { + return this.getMostFrequentUrls_(this.completed, num); + }, + + + /** + * Get a list of the X most errored URLs. + * + * @param {number=} num The number of unsuccessful navigation requests to + * return. If 0 is passed in, or the argument left off entirely, all + * successful requests are returned. + * @return {Object.} The list of + * unsuccessful navigation requests, sorted in decending order + * of frequency. + */ + getMostErroredUrls: function(num) { + return this.getMostErroredUrls_(this.errored, num); + }, + + + /** + * Get a list of the most frequent URLs in a list. + * + * @param {NavigationCollector.Request} list A list of URLs to parse. + * @param {number=} num The number of navigation requests to return. If + * 0 is passed in, or the argument left off entirely, all requests + * are returned. + * @return {Object.} The list of + * navigation requests, sorted in decending order of frequency. + * @private + */ + getMostFrequentUrls_: function(list, num) { + var result = []; + var avg; + // Convert the 'completed_' object to an array. + for (var x in list) { + avg = 0; + if (list.hasOwnProperty(x) && list[x].length) { + list[x].forEach(function(o) { + avg += o.duration; + }); + avg = avg / list[x].length; + result.push({ + url: x, + numRequests: list[x].length, + requestList: list[x], + average: avg + }); + } + } + // Sort the array. + result.sort(function(a, b) { + return b.numRequests - a.numRequests; + }); + // Return the requested number of results. + return num ? result.slice(0, num) : result; + } +}; diff --git a/basic_8/popup.css b/basic_8/popup.css new file mode 100644 index 0000000..e551226 --- /dev/null +++ b/basic_8/popup.css @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2011 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. + */ + + +body { + margin: 5px 10px 10px; +} + +h1 { + color: #53637D; + font: 26px/1.2 Helvetica, sans-serif; + font-size: 200%; + margin: 0; + padding-bottom: 4px; + text-shadow: white 0 1px 2px; +} + +body > section { + border-radius: 5px; + background: -webkit-linear-gradient(rgba(234, 238, 243, 0.2), #EAEEF3), + -webkit-linear-gradient( + left, #EAEEF3, #EAEEF3 97%, #D3D7DB); + font: 14px/1 Arial,Sans Serif; + padding: 10px; + width: 563px; + max-height: 400px; + overflow-y: auto; + box-shadow: inset 0px 2px 5px rgba(0,0,0,0.5); +} + +body > section > ol { + padding: 0; + margin: 0; + list-style: none inside; +} + +body > section > ol > li { + position: relative; + margin: 0.5em 0 0.5em 40px; +} + +code { + word-wrap: break-word; + background: rgba(255,255,0, 0.5); +} + +em { + position: absolute; + top: 0px; + left: -40px; + width: 30px; + text-align: right; + font: 30px/1 Helvetica, sans-serif; + font-weight: 700; +} + +p { + min-height: 30px; + line-height: 1.2; +} diff --git a/basic_8/popup.html b/basic_8/popup.html new file mode 100644 index 0000000..1f51802 --- /dev/null +++ b/basic_8/popup.html @@ -0,0 +1,17 @@ + + + + + WebNavigation Tech Demo Popup + + + +

Most Requested URLs

+
+ + + diff --git a/basic_8/popup.js b/basic_8/popup.js new file mode 100644 index 0000000..ccb3243 --- /dev/null +++ b/basic_8/popup.js @@ -0,0 +1,36 @@ +// Copyright (c) 2011 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. + +/** + * @filedescription Initializes the extension's popup page. + */ + +chrome.runtime.sendMessage( + {'type': 'getMostRequestedUrls'}, + function generateList(response) { + var section = document.querySelector('body>section'); + var results = response.result; + var ol = document.createElement('ol'); + var li, p, em, code, text; + var i; + for (i = 0; i < results.length; i++ ) { + li = document.createElement('li'); + p = document.createElement('p'); + em = document.createElement('em'); + em.textContent = i + 1; + code = document.createElement('code'); + code.textContent = results[i].url; + text = document.createTextNode( + chrome.i18n.getMessage('navigationDescription', + [results[i].numRequests, + results[i].average])); + p.appendChild(em); + p.appendChild(code); + p.appendChild(text); + li.appendChild(p); + ol.appendChild(li); + } + section.innerHTML = ''; + section.appendChild(ol); + }); diff --git a/broken-links/README.md b/broken-links/README.md index 1dccbc5..58980b1 100644 --- a/broken-links/README.md +++ b/broken-links/README.md @@ -11,8 +11,8 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [experimental.devtools.audits.addCategory](http://developer.chrome.com/extensions/experimental.devtools.audits.html#method-addCategory) -* [extension.onRequest](http://developer.chrome.com/extensions/extension.html#event-onRequest) -* [extension.sendRequest](http://developer.chrome.com/extensions/extension.html#method-sendRequest) -* [tabs.executeScript](http://developer.chrome.com/extensions/tabs.html#method-executeScript) -* [tabs.sendRequest](http://developer.chrome.com/extensions/tabs.html#method-sendRequest) \ No newline at end of file +* [experimental.devtools.audits.addCategory](https://developer.chrome.com/extensions/experimental.devtools.audits#method-addCategory) +* [extension.onRequest](https://developer.chrome.com/extensions/extension#event-onRequest) +* [extension.sendRequest](https://developer.chrome.com/extensions/extension#method-sendRequest) +* [tabs.executeScript](https://developer.chrome.com/extensions/tabs#method-executeScript) +* [tabs.sendRequest](https://developer.chrome.com/extensions/tabs#method-sendRequest) \ No newline at end of file diff --git a/buildbot/README.md b/buildbot/README.md index 2117d19..af00331 100644 --- a/buildbot/README.md +++ b/buildbot/README.md @@ -11,11 +11,11 @@ Content is licensed under the [Google BSD License](http://code.google.com/google Calls ----- -* [browserAction.setBadgeBackgroundColor](http://developer.chrome.com/extensions/browserAction.html#method-setBadgeBackgroundColor) -* [browserAction.setBadgeText](http://developer.chrome.com/extensions/browserAction.html#method-setBadgeText) -* [browserAction.setTitle](http://developer.chrome.com/extensions/browserAction.html#method-setTitle) -* [extension.getBackgroundPage](http://developer.chrome.com/extensions/extension.html#method-getBackgroundPage) -* [extension.getURL](http://developer.chrome.com/extensions/extension.html#method-getURL) -* [extension.getViews](http://developer.chrome.com/extensions/extension.html#method-getViews) -* [storage.StorageArea.get](http://developer.chrome.com/extensions/storage.html#method-StorageArea-get) -* [storage.StorageArea.set](http://developer.chrome.com/extensions/storage.html#method-StorageArea-set) \ No newline at end of file +* [browserAction.setBadgeBackgroundColor](https://developer.chrome.com/extensions/browserAction#method-setBadgeBackgroundColor) +* [browserAction.setBadgeText](https://developer.chrome.com/extensions/browserAction#method-setBadgeText) +* [browserAction.setTitle](https://developer.chrome.com/extensions/browserAction#method-setTitle) +* [extension.getBackgroundPage](https://developer.chrome.com/extensions/extension#method-getBackgroundPage) +* [extension.getURL](https://developer.chrome.com/extensions/extension#method-getURL) +* [extension.getViews](https://developer.chrome.com/extensions/extension#method-getViews) +* [storage.StorageArea.get](https://developer.chrome.com/extensions/storage#method-StorageArea-get) +* [storage.StorageArea.set](https://developer.chrome.com/extensions/storage#method-StorageArea-set) \ No newline at end of file diff --git a/buildbot/bg.js b/buildbot/bg.js index a351678..8324e03 100644 --- a/buildbot/bg.js +++ b/buildbot/bg.js @@ -25,12 +25,12 @@ function updateBadgeOnErrorStatus() { var lastNotification = null; function notifyStatusChange(treeState, status) { if (lastNotification) - lastNotification.cancel(); + lastNotification.close(); - var notification = webkitNotifications.createNotification( - chrome.extension.getURL("icon.png"), "Tree is " + treeState, status); - lastNotification = notification; - notification.show(); + lastNotification = new Notification("Tree is " + treeState, { + icon: chrome.extension.getURL("icon.png"), + body: status + }); } // The type parameter should be "open", "closed", or "throttled". diff --git a/buildbot/manifest.json b/buildbot/manifest.json index 1c1f5e0..0d86bcb 100644 --- a/buildbot/manifest.json +++ b/buildbot/manifest.json @@ -1,6 +1,6 @@ { "name": "Chromium Buildbot Monitor", - "version": "0.8.4", + "version": "0.9.0", "description": "Displays the status of the Chromium buildbot in the toolbar. Click to see more detailed status in a popup.", "icons": { "128": "icon.png" }, "background": { @@ -23,6 +23,9 @@ "default_popup": "popup.html" }, "options_page": "options.html", - + "options_ui": { + "chrome_style": true, + "page": "options.html" + }, "manifest_version": 2 } diff --git a/buildbot/options.html b/buildbot/options.html index 539f7c8..e045aa6 100644 --- a/buildbot/options.html +++ b/buildbot/options.html @@ -2,10 +2,13 @@ Chromium Buildbot Monitor Options + -

Options for Chromium Buildbot Monitor

-