| Index: chrome/common/extensions/docs/examples/extensions/storage_api_devtools/storage/panel.js
|
| ===================================================================
|
| --- chrome/common/extensions/docs/examples/extensions/storage_api_devtools/storage/panel.js (revision 0)
|
| +++ chrome/common/extensions/docs/examples/extensions/storage_api_devtools/storage/panel.js (revision 0)
|
| @@ -0,0 +1,561 @@
|
| +// 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.
|
| +
|
| +// Called by devtools.js to add content script or main world contexts.
|
| +var onContextAdded = function(contexts){};
|
| +// Called by panel.js to notify that the inspected window has unloaded.
|
| +var onInspectedWindowUnloaded = function(){};
|
| +
|
| +// Escape key names starting with '$' since they are ignored by AngularJS.
|
| +function escapeKey(key) {
|
| + if (key[0] == '$' || key[0] == '_')
|
| + return '_' + key;
|
| + else
|
| + return key;
|
| +}
|
| +
|
| +function unescapeKey(key) {
|
| + if (key[0] == '_')
|
| + return key.substr(1);
|
| + else
|
| + return key;
|
| +}
|
| +
|
| +function StorageDevTools() {
|
| + var contexts = [];
|
| + var current = -1;
|
| + var port;
|
| + var devtoolsId;
|
| + var devtoolsRegistered = false;
|
| + var storage = this;
|
| +
|
| + // Events
|
| + // Fired when execution contexts have been added.
|
| + this.onContextAdded = function() {};
|
| + // Fired when the model of the current context changed.
|
| + this.onModelUpdated = function() {};
|
| + // Fired when the inspected window navigates or closes.
|
| + this.onInvalidated = function() {};
|
| + // Fired when an error occurred.
|
| + this.onError = function(details) {};
|
| +
|
| + function initializeContexts() {
|
| + contexts.forEach(function(context) {
|
| + if (!context.initialized) {
|
| + context.initialized = true;
|
| + var options = {};
|
| + if (context.type == 'contentScripts')
|
| + options = {scriptExecutionContext: context.securityOrigin};
|
| + evalScript('/storage/inspectedWindow/initStorage.js', options,
|
| + {devtoolsId: devtoolsId, reportUsage: 'true',
|
| + contextId: context.id});
|
| + }
|
| + });
|
| + }
|
| +
|
| + function onDevToolsMessage(message) {
|
| + var prefix = 'devtools.';
|
| + if (message && message.name) {
|
| + if (message.name == '_inspectedWindowUnload') {
|
| + current = -1;
|
| + contexts = [];
|
| + storage.onInvalidated();
|
| + window.onInspectedWindowUnloaded(); // exported by devtools.js
|
| + } else if (message.name.substr(0, prefix.length) == prefix &&
|
| + message.contextId != undefined)
|
| + onInspectedWindowMessage(message.name.substr(prefix.length),
|
| + message);
|
| + else
|
| + console.warn('Unknown message: ', message);
|
| + }
|
| + }
|
| +
|
| + onContextAdded = (function(contexts_) {
|
| + contexts_.forEach(function(cur) {
|
| + cur.id = cur.extensionId || '<main_world>';
|
| + cur.model = newStorageModel();
|
| + });
|
| + contexts = contexts.concat(contexts_);
|
| +
|
| + if (contexts.length && current == -1)
|
| + current = 0;
|
| +
|
| + if (!devtoolsRegistered) {
|
| + devtoolsRegistered = true;
|
| + registerDevToolsPanel('storage', function(devtoolsId_, port_) {
|
| + devtoolsId = devtoolsId_; // Used in initializeContexts().
|
| + port = port_;
|
| + port.onMessage.addListener(onDevToolsMessage);
|
| +
|
| + initializeContexts();
|
| + });
|
| + } else if (port && devtoolsId)
|
| + initializeContexts();
|
| +
|
| + storage.onContextAdded();
|
| + });
|
| +
|
| + function newStorageModel() {
|
| + return {
|
| + local: {
|
| + items: {},
|
| + newItem: {},
|
| + stat: {
|
| + itemNumber: 0,
|
| + totalBytes: 0,
|
| + maxItemLength: 0,
|
| + },
|
| + limits: {
|
| + quotaBytes: chrome.storage.local.QUOTA_BYTES
|
| + }
|
| + },
|
| + sync: {
|
| + items: {},
|
| + newItem: {},
|
| + stat: {
|
| + itemNumber: 0,
|
| + totalBytes: 0,
|
| + maxItemLength: 0,
|
| + },
|
| + limits: {
|
| + quotaBytes: chrome.storage.sync.QUOTA_BYTES,
|
| + quotaBytesPerItem: chrome.storage.sync.QUOTA_BYTES_PER_ITEM,
|
| + maxItems: chrome.storage.sync.MAX_ITEMS,
|
| + maxWriteOperationsPerHour:
|
| + chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_HOUR,
|
| + maxSustainedWriteOperationsPerMinute:
|
| + chrome.storage.sync.MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE
|
| + }
|
| + }
|
| + };
|
| + }
|
| +
|
| + this.currentModel = (function() {
|
| + if (contexts.length == 0)
|
| + return {};
|
| + else
|
| + return contexts[current].model;
|
| + });
|
| +
|
| + this.modelFromContext = (function(contextId) {
|
| + var result = null;
|
| + contexts.forEach(function(cur) {
|
| + if (cur.id == contextId)
|
| + result = cur.model;
|
| + });
|
| + return result;
|
| + });
|
| +
|
| + this.switchContext = (function(id) {
|
| + contexts.forEach(function(cur, index) {
|
| + if (cur.id == id)
|
| + current = index;
|
| + });
|
| + return this.currentModel();
|
| + });
|
| +
|
| + this.getContexts = (function() {
|
| + var result = [];
|
| + contexts.forEach(function(cur, index) {
|
| + result.push({
|
| + id: cur.id,
|
| + index: index,
|
| + type: cur.type,
|
| + securityOrigin: cur.securityOrigin
|
| + });
|
| + });
|
| + return result;
|
| + });
|
| +
|
| + this.getCurrentContextIndex = (function() {
|
| + return current;
|
| + });
|
| +
|
| + this.reload = (function() {
|
| + current = -1;
|
| + contexts = [];
|
| + window.devtoolsReload(); // Exported by devtools.js
|
| + });
|
| +
|
| + function checkAreaName(areaName) {
|
| + return areaName == 'local' || areaName == 'sync';
|
| + }
|
| +
|
| + function updateStorageItemNumber(model, areaName) {
|
| + var area = model[areaName];
|
| + area.stat.itemNumber = Object.keys(area.items).length;
|
| + }
|
| +
|
| + function updateStorageBytesInUse(model, areaName) {
|
| + var area = model[areaName];
|
| + area.stat.totalBytes = Object.keys(area.items).reduce(function(prev, cur) {
|
| + return prev + (area.items[cur].bytesInUse || 0);
|
| + }, 0);
|
| + area.stat.maxItemLength = Object.keys(area.items).reduce(
|
| + function(prev, cur) {
|
| + return Math.max(prev, area.items[cur].bytesInUse || 0);
|
| + }, 0);
|
| + }
|
| +
|
| + function setStorageUnlimited(model, result) {
|
| + if (result)
|
| + model.local.limits.quotaBytes = 0;
|
| + }
|
| +
|
| + function appendStorageItem(model, areaName, key, value) {
|
| + model[areaName].items[escapeKey(key)] = {value: value};
|
| + updateStorageItemNumber(model, areaName);
|
| + }
|
| +
|
| + function updateStorageItem(model, areaName, key, details) {
|
| + var item_details = model[areaName].items[escapeKey(key)];
|
| + if (!item_details) {
|
| + if (details.value) {
|
| + // (Is it possible that reportUsage is handled first?)
|
| + appendStorageItem(model, areaName, key, details.value);
|
| + }
|
| + return;
|
| + }
|
| + if (details.value != undefined)
|
| + item_details.value = details.value;
|
| + if (details.bytesInUse != undefined) {
|
| + item_details.bytesInUse = details.bytesInUse;
|
| + updateStorageBytesInUse(model, areaName);
|
| + }
|
| + }
|
| +
|
| + function removeStorageItem(model, areaName, key) {
|
| + delete model[areaName].items[escapeKey(key)];
|
| + updateStorageItemNumber(model, areaName);
|
| + updateStorageBytesInUse(model, areaName);
|
| + }
|
| +
|
| + function initStorageArea(model, areaName, items) {
|
| + Object.keys(items).forEach(function(key) {
|
| + appendStorageItem(model, areaName, key, items[key], false);
|
| + });
|
| + }
|
| +
|
| + function onInspectedWindowMessage(name, message) {
|
| + var model = storage.modelFromContext(message.contextId);
|
| + if (!model)
|
| + return;
|
| + switch(name) {
|
| + case 'init':
|
| + if (message.areaName && message.items &&
|
| + checkAreaName(message.areaName))
|
| + initStorageArea(model, message.areaName, message.items);
|
| + storage.onModelUpdated();
|
| + break;
|
| + case 'reportUsage':
|
| + if (message.areaName && message.key &&
|
| + (typeof message.bytesInUse) == 'number' &&
|
| + checkAreaName(message.areaName))
|
| + updateStorageItem(model, message.areaName, message.key,
|
| + {bytesInUse: message.bytesInUse});
|
| + storage.onModelUpdated();
|
| + break;
|
| + case 'update':
|
| + if (message.areaName && message.changes &&
|
| + checkAreaName(message.areaName)) {
|
| + Object.keys(message.changes).forEach(function(key) {
|
| + var change = message.changes[key];
|
| + if (change.newValue != undefined)
|
| + updateStorageItem(model, message.areaName, key,
|
| + {value: change.newValue});
|
| + else
|
| + removeStorageItem(model, message.areaName, key);
|
| + });
|
| + storage.onModelUpdated();
|
| + }
|
| + break;
|
| + case 'unlimitedStorage':
|
| + if (message.unlimitedStorage != undefined) {
|
| + setStorageUnlimited(model, message.unlimitedStorage);
|
| + storage.onModelUpdated();
|
| + }
|
| + break;
|
| + case 'reportError':
|
| + if (message.lastError)
|
| + storage.onError(message.lastError);
|
| + break;
|
| + default:
|
| + console.warn('Unknown devtools.* message: ', message);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + this.updateInspectedWindowStorage = function(areaName, updateType, extra) {
|
| + if (!extra)
|
| + extra = {};
|
| + if (contexts[current])
|
| + extra.contextId = contexts[current].id;
|
| + extra.areaName = areaName;
|
| + extra.name = updateType;
|
| + port.postMessage(extra);
|
| + }
|
| +
|
| + return this;
|
| +}
|
| +
|
| +angular.module('StorageData', []).directive('ngBlur', function() {
|
| + return function($scope, element, attributes) {
|
| + element.bind('blur', function() {
|
| + $scope.$apply(attributes.ngBlur);
|
| + });
|
| + };
|
| +}).
|
| +directive('ngFocusIf', function() {
|
| + return function($scope, element, attributes) {
|
| + $scope.$watch(attributes.ngFocusIf, function(newValue, oldValue) {
|
| + if (newValue)
|
| + {
|
| + element[0].style.display = '';
|
| + element[0].focus();
|
| + }
|
| + });
|
| + };
|
| +}).
|
| +directive('ngI18nContent', function() {
|
| + return function($scope, element, attributes) {
|
| + element[0].appendChild(document.createTextNode(chrome.i18n.getMessage(
|
| + attributes.ngI18nContent)));
|
| + };
|
| +}).
|
| +directive('ngI18nReorder', function() {
|
| + return function($scope, element, attributes) {
|
| + var e = element[0];
|
| + e.style.display = 'flex';
|
| + if (e.style.display != 'flex')
|
| + e.style.display = '-webkit-flex';
|
| + var order = chrome.i18n.getMessage(attributes.ngI18nReorder).split(/\s+/);
|
| + order.forEach(function(cur, index) {
|
| + if (e.children[index])
|
| + e.children[index].style.webkitOrder =
|
| + e.children[index].style.webkitOrder = cur;
|
| + });
|
| + };
|
| +}).
|
| +// data-bind-chrome-storage="{area: 'local|sync', key: 'keyName',
|
| +// model: ..., [defaultValue: ...]}"
|
| +directive('bindChromeStorage', function() {
|
| + return function($scope, element, attributes) {
|
| + var details = $scope.$eval(attributes.bindChromeStorage);
|
| +
|
| + // Update storage when model changes.
|
| + $scope.$watch(details.model, function(newValue, oldValue) {
|
| + var items = {};
|
| + items[details.key] = $scope.$eval(details.model);
|
| + chrome.storage[details.area].set(items);
|
| + });
|
| +
|
| + // Initialize model from storage or default value.
|
| + chrome.storage[details.area].get(details.key, function(items) {
|
| + function apply(value) {
|
| + $scope.$apply(details.model + ' = ' + JSON.stringify(value));
|
| + }
|
| + if (items[details.key] != undefined)
|
| + apply(items[details.key]);
|
| + else if (details.defaultValue != undefined)
|
| + apply(details.defaultValue);
|
| + });
|
| + };
|
| +}).
|
| +factory('storage', function() {
|
| + var storage = new StorageDevTools();
|
| + return storage;
|
| +});
|
| +
|
| +function StorageDataCtrl($scope, storage) {
|
| + var kLastUsedArea = '____/lastUsedArea';
|
| + $scope.available = true;
|
| + $scope.filterOrHighlight = 'filter';
|
| + $scope.filterField = 'keyValue';
|
| + chrome.storage.sync.get(kLastUsedArea, function(items) {
|
| + if (items[kLastUsedArea] && !$scope.currentArea)
|
| + $scope.currentArea = items[kLastUsedArea];
|
| + });
|
| +
|
| + storage.onModelUpdated = (function() {
|
| + $scope.$apply(function($scope){});
|
| + });
|
| +
|
| + storage.onInvalidated = (function() {
|
| + $scope.available = false;
|
| + $scope.$apply(function($scope){});
|
| + });
|
| +
|
| + storage.onContextAdded = (function() {
|
| + $scope.available = true;
|
| + $scope.contexts = storage.getContexts();
|
| + // This assumes that $scope.contexts and contexts in storage are
|
| + // arranged in the same order.
|
| + $scope.currentContext = $scope.contexts[
|
| + storage.getCurrentContextIndex()] || {};
|
| + $scope.storage = storage.currentModel();
|
| + $scope.switchArea($scope.currentArea || 'local');
|
| + });
|
| +
|
| + storage.onError = (function(details) {
|
| + alert(details.message);
|
| + });
|
| +
|
| + $scope.firstEditPrompt = (function() {
|
| + var key = '____/firstEdit';
|
| + chrome.storage.sync.get(key, function(items) {
|
| + if (!items[key] && !$scope.readonly) {
|
| + alert(chrome.i18n.getMessage('panels_storage_firstTimeEditWarning'));
|
| + items[key] = true;
|
| + chrome.storage.sync.set(items);
|
| + }
|
| + });
|
| + });
|
| +
|
| + $scope.switchContext = (function(context) {
|
| + $scope.storage = storage.switchContext(context.id);
|
| + $scope.switchArea($scope.currentArea);
|
| + });
|
| +
|
| + $scope.switchArea = (function(area) {
|
| + var current = {};
|
| + current[area] = $scope.storage[area];
|
| + $scope.current = current;
|
| + $scope.currentArea = area;
|
| + var items = {};
|
| + items[kLastUsedArea] = area;
|
| + chrome.storage.sync.set(items);
|
| + });
|
| +
|
| + $scope.reinit = (function() {
|
| + storage.reload();
|
| + });
|
| +
|
| + $scope.unescapeKey = unescapeKey;
|
| +
|
| + $scope.beforeEditValue = (function(details) {
|
| + if ($scope.readonly)
|
| + return;
|
| + details.valueString = $scope.dumpValue(details.value);
|
| + details.editingValue = true;
|
| + });
|
| +
|
| + $scope.onEditValue = (function(details) {
|
| + var valid = true;
|
| + try {
|
| + JSON.parse(details.valueString);
|
| + } catch (e) {
|
| + valid = false;
|
| + }
|
| + details.invalid = !valid;
|
| + });
|
| +
|
| + $scope.editValue = (function(areaName, key, details) {
|
| + if (details.invalid)
|
| + return;
|
| + // TODO Use JSON or object?
|
| + storage.updateInspectedWindowStorage(areaName, 'updateValue',
|
| + {key: unescapeKey(key), value: details.valueString});
|
| + details.editingValue = false;
|
| + });
|
| +
|
| + $scope.beforeEditKey = (function(key, details) {
|
| + if ($scope.readonly)
|
| + return;
|
| + details.keyString = unescapeKey(key);
|
| + details.editingKey = true;
|
| + });
|
| +
|
| + $scope.editKey = (function(areaName, key, details) {
|
| + if (unescapeKey(key) != details.keyString)
|
| + storage.updateInspectedWindowStorage(areaName, 'updateKey',
|
| + {key: unescapeKey(key), newKey: details.keyString});
|
| + details.editingKey = false;
|
| + });
|
| +
|
| + $scope.remove = (function(areaName, key) {
|
| + if ($scope.readonly)
|
| + return;
|
| + storage.updateInspectedWindowStorage(areaName, 'removeKey',
|
| + {key: unescapeKey(key)});
|
| + });
|
| +
|
| + $scope.clear = (function(areaName) {
|
| + if ($scope.readonly)
|
| + return;
|
| + if (window.confirm(chrome.i18n.getMessage('panels_storage_confirmClear')))
|
| + storage.updateInspectedWindowStorage(areaName, 'clear');
|
| + });
|
| +
|
| + $scope.onEditNewItem = (function(properties) {
|
| + var valid = true;
|
| + try {
|
| + JSON.parse(properties.newItem.value);
|
| + } catch (e) {
|
| + valid = false;
|
| + }
|
| + properties.newItem.keyInvalid =
|
| + (!properties.newItem.key || properties.items[properties.newItem.key]) ?
|
| + true : false;
|
| + properties.newItem.valueInvalid = !valid;
|
| + if (valid)
|
| + properties.newItem.size = properties.newItem.key.length +
|
| + properties.newItem.value.length;
|
| + });
|
| +
|
| + $scope.addNewItem = (function(areaName, properties) {
|
| + if (properties.newItem.keyInvalid ||
|
| + properties.newItem.valueInvalid ||
|
| + !properties.newItem.value ||
|
| + !properties.newItem.key)
|
| + return;
|
| + storage.updateInspectedWindowStorage(areaName, 'updateValue',
|
| + {key: properties.newItem.key, value: properties.newItem.value});
|
| + properties.newItem = {};
|
| + });
|
| +
|
| + $scope.dumpValue = (function(value) {
|
| + return JSON.stringify(value, null, ' ');
|
| + });
|
| +
|
| + function matchQuery(str) {
|
| + return str.indexOf($scope.query) != -1;
|
| + }
|
| +
|
| + function filterItem(key, details) {
|
| + var match = false;
|
| +
|
| + switch($scope.filterField) {
|
| + case 'key':
|
| + match = matchQuery(key);
|
| + break;
|
| + case 'value':
|
| + match = matchQuery(JSON.stringify(details.value));
|
| + break;
|
| + case 'keyValue':
|
| + match = matchQuery(key) || matchQuery(JSON.stringify(details.value));
|
| + break;
|
| + }
|
| +
|
| + if ($scope.filterOrHighlight == 'filter') {
|
| + details.hidden = !match;
|
| + delete details.highlight;
|
| + } else {
|
| + details.highlight = match;
|
| + delete details.hidden;
|
| + }
|
| + }
|
| +
|
| + $scope.filterItems = (function() {
|
| + if ($scope.query) {
|
| + var items = $scope.current[$scope.currentArea].items;
|
| + for (key in items)
|
| + filterItem(key, items[key]);
|
| + }
|
| + });
|
| +
|
| + $scope.percentage = (function(value) {
|
| + return Math.round(value * 100).toString() + ' %';
|
| + });
|
| +}
|
| +
|
|
|
| Property changes on: chrome/common/extensions/docs/examples/extensions/storage_api_devtools/storage/panel.js
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
| Added: svn:mime-type
|
| + text/javascript
|
|
|
|
|